#Librerias
library(tidyverse)
library(tidymodels)
library(GGally)
library(ggplot2)
library(MASS)
library(robustbase)
library(dplyr)
library(corrplot)
library(caret)
library(viridis)
library(gridExtra)
library(kableExtra)

Objetivo

El fútbol es el deporte más popular, el que tiene un mercado más grande y en el cual el valor de las transferencias de los jugadores es el más alto. En ese sentido, nos proponemos analizar las transferencias y cotizaciones de los jugadores de las cinco ligas mas importantes de Europa:

  • La Liga - España
  • Premier League - Inglaterra
  • Bundesliga - Alemania
  • Serie A - Italia
  • Ligue 1 - Francia

Por este motivo, y haciendo uso de los conocimientos aportados por la materia, optamos por profundizar en el desarrollo de métodos de regresión robustos para hacer análisis predictivos, haciendo foco en la explicabilidad del modelo para predecir las transferencias de los jugadores.

El objetivo del presente trabajo consiste en la utilización distintos métodos robustos para predecir la variable objetivo: el precio de transferencia de los jugadores.

  • Observar si el país de origen es una variable explicativa con respecto a la variable objetivo.
  • Identificar si los ratios que demuestran el desempeño de los jugadores generan impacto sobre la transferencia de los jugadores.
  • Utilizar la regresión regresión de mínimos cuadrados ponderados, regresión de mínimos cuadrados robustos (Huber).
  • Comparar el desempeño de los distintos modelos para predecir la variable objetivo.

Introducción

En el método lineal clásico se utiliza el método de cuadrados mínimos para encontrar los parámetros \(\beta\).

La función de pérdida que se quiere minimizar es la suma del cuadrado de los residuos.

\[ g(a, b) = \sum_{i=1}^n \left( Y_i - \left( a + b X_i \right) \right)^2 \quad \text{(1)} \]

En los modelos lineales robustos queremos cambiar la función de perdida tal que:

  • Sea Insensible a valores extremadamente grandes o outliers(o residuos grandes)
  • Crezca menos que mínimos cuadrados cuando miramos lo suficientemente lejos del cero
  • Tenga Alta eficiencia: si los datos de la muestra siguieran el modelo de regresión con errores normales, queremos que el estimador (beta) que el método robusta calcula se parezca al de mínimos cuadrados, por lo que la función de pérdida que el método robusto calcula debería parecerse al de mínimos cuadrado.

\[ g(a, b) = \sum_{i=1}^n \rho \left( \frac{Y_i - \left( a + b X_i \right)}{s_n} \right) \quad \text{(2)} \]

Definición de la función ρ

Sea ρ: R → R una función que cumple las siguientes propiedades:

  1. Dominio y Codominio
    • ρ: R → R
  2. Propiedades Fundamentales
    • ρ(0) = 0
    • ρ(-u) = ρ(u) [Simetría]
    • Si 0 ≤ u ≤ v entonces ρ(u) ≤ ρ(v) [Monotonía]
    • ρ es continua
    • sup{ρ(u): u ∈ R} = 1 [Acotación]
  3. Propiedad de Crecimiento Estricto
    • Si ρ(u) < 1 y 0 ≤ u < v entonces ρ(u) < ρ(v)

Esta función ρ es acotada, creciente y simétrica alrededor del cero, características que la hacen especialmente útil para la estimación robusta.

Una posibilidad es ajustar una recta usando un procedimiento de ajuste robusto, por ejemplo un MM-estimador de regresión, propuesto por Yohai [1987]. En R, esto está programado dentro de la rutina lmrob en el paquete robustbase de R. La estimación se hace en tres etapas, se propone un estimador inicial de los parámetros, a partir de él se estima a sn y manualmente se obtienen los estimadores de los parámetros a partir de ellos, minimizando la función objetivo

Existen varias funciones \(\rho\) que serán evaluadas en el presente trabajo. Por defecto, robustbase utiliza la bicuadrada (bisquare), pero también se pueden implementar lqq, welsh, optimal, etc.

Robustbase utiliza el algoritmo Iteratively Reweighted Least Squares (IRWLS) para estimar los parámetros \(\beta\). El proceso consiste en la siguientes etapas:

  1. Inicializar el proceso utilizando un estimación inicial de a y b utilizando el método de cuadrados mínimos.

  2. Derivar la función (2) respecto a los parámetros a y b: \[ \frac{\partial g}{\partial a} = \sum_{i=1}^{n} \psi \left(\frac{Y_i - (a + bX_i)}{s_n} \right) \cdot \frac{-1}{s_n} \quad \text{(3)} \] \[ \frac{\partial g}{\partial b} = \sum_{i=1}^{n} \psi \left(\frac{Y_i - (a + bX_i)}{s_n} \right) \cdot \frac{-X_i}{s_n} \quad \text{(4)} \]

donde

\[ \frac{d \rho(x)}{dx} = \psi(x) \quad \text{(5)} \]

  1. Se calculan los pesos de cada observacion

\[ w_i = \frac{\psi \left(\frac{Y_i - (a + bX_i)}{s} \right)}{\frac{Y_i - (a + bX_i)}{s}} \quad \text{(6)} \]

  1. Se multiplica a cada observación por su respectivo peso.

  2. Se vuelven a estimar los parámetros a y b con una regresión ponderada.

Se repiten los pasos hasta no observar mas mejoras o hasta un máximo de iteraciones.

A continuación se muestran las funciones \(\rho\) más comunes y como se comportan los pesos de las mismas.

  1. Bisquare (Tukey): \[ \rho(x) = \begin{cases} 1 - (1 - (x/k)^2)^3 & \text{si } |x| \leq k \\ 1 & \text{si } |x| > k \end{cases} \quad \text{(7)} \]
# set margins for plots
options(SweaveHooks=list(fig=function() par(mar=c(3,3,1.4,0.7),
                                            mgp=c(1.5, 0.5, 0))))
## x axis for plots:
x. <- seq(-5, 10, length.out = 1501)
source(system.file("xtraR/plot-psiFun.R", package = "robustbase", mustWork=TRUE))
getOption("SweaveHooks")[["fig"]]()
p.psiFun(x., "biweight", par = 4.685)

  1. Huber: \[ \rho(x) = \begin{cases} \frac{x^2}{2} & \text{si } |x| \leq k \\ k|x| - \frac{k^2}{2} & \text{si } |x| > k \end{cases} \quad \text{(8)} \]
getOption("SweaveHooks")[["fig"]]()
plot(huberPsi, x., ylim=c(-1.4, 5), leg.loc="topright", main=FALSE)

  1. Welsh: \[ \rho(x) = 1 - \exp(-x^2/k^2) \quad \text{(9)} \]
getOption("SweaveHooks")[["fig"]]()
p.psiFun(x., "Welsh", par = 2.11)

  1. Hampel: \[ \rho(x) = \begin{cases} \frac{x^2}{2} & \text{si } |x| \leq a \\ a|x| - \frac{a^2}{2} & \text{si } a < |x| \leq b \\ \frac{a(c|x| - x^2/2 - bc + b^2/2)}{c-b} & \text{si } b < |x| \leq c \\ a(c - \frac{b}{2}) & \text{si } |x| > c \end{cases} \quad \text{(10)} \]
getOption("SweaveHooks")[["fig"]]()
## see also hampelPsi
p.psiFun(x., "Hampel", par = ## Default, but rounded:
           round(c(1.5, 3.5, 8) * 0.9016085, 1))

  1. LQQ (Linearly Quadratically Quadratic): \[ \rho(x) = \begin{cases} \frac{x^2}{2} & \text{si } |x| \leq c_1 \\ c_1|x| - \frac{c_1^2}{2} & \text{si } c_1 < |x| \leq c_2 \\ \frac{(c_3|x| - x^2/2)}{c_3-c_2} & \text{si } c_2 < |x| \leq c_3 \\ c_3 & \text{si } |x| > c_3 \end{cases} \quad \text{(11)} \]
getOption("SweaveHooks")[["fig"]]()
p.psiFun(x., "LQQ", par = c(-.5,1.5,.95,NA))

  1. Optimal \[ \psi(x) = \begin{cases} x & \text{si } |x| \leq a \\ a \cdot \text{sign}(x) \cdot \left(1 - \left(\frac{|x| - a}{b - a}\right)^2\right)^2 & \text{si } b < |x| \leq c \\ 0 & \text{si } |x| > c \end{cases} \quad \text{(12)} \]
getOption("SweaveHooks")[["fig"]]()
p.psiFun(x., "optimal", par = 1.06, leg.loc="bottomright")

Donde \(k\), \(a\), \(b\), \(c\), \(c_1\), \(c_2\) y \(c_3\) son constantes que determinan los puntos de quiebre y la forma de cada función.

Existen muchas otras propuestas de estimadores robustos para regresión, por ejemplo LMS (least median of squares), LTS (least trimmed squares), τ−estimadores de regresión, y casi todas están implementadas en R.

Dataset

El set de datos consiste en dos bases, ambas obtenidas de Kaggle.

La base de datos con los precios de los jugadores se obtuvo de la web Transfermarkt. Cuenta con 32405 registros, y dos de sus variables son los precios actuales de los jugadores y el precio mas alta alcanzado. Además cuenta con la información del club y liga actual de cada jugador.

Las otra base de datos cuenta con las métricas durante la temporada 2023/2024 de los jugadores de las 5 ligas mas importantes del fútbol europeo. Algunas de sus variables son Edad, goles anotados, asistencias, posición dentro del campo de juego, nacionalidad, entre otras.

Ambas bases de datos se unieron por el nombre y apellido del jugador, y el club actual. Adicionalmente, se agregó la variable Continente al dataset final, la cual informa el continente natal de cada jugador.

El dataset se divide en Entrenamiento y Prueba, de manera estratificada según el precio. Se utiliza el siguiente boxplot para generar una nueva categoría que categorice según el precio del jugador.

Luego utilizaremos esta nueva variable para realizar la división del dataset de manera estratificada.

Categorías de la nueva variable

  • Muy bajo: Menor a mediana (Q2)
  • Bajo: entre mediana (Q2) y cuartil superior (Q3)
  • Medio: entre cuartil superior (Q3) y límite superior (Q3 + 1.5 x IQR)
  • Alto: entre limite superior y €100.000.000
  • Muy Alto: mayor a €100.000.000 (representa los 10 jugadores mas caros)

El objetivo es que quedan estratificados los jugadores mas caros u outliers, por eso no se realiza una división por debajo de Q2.

# Carga de datset
# setwd("/Users/jorgefernandez/Documents/Cienciadedatos/EEA2024/TP02")
setwd("/Users/rmarques/UBA/EEA/german-eea-2024/TP02")
df <- as.data.frame(read.csv("dataset.csv"))
df <- as.data.frame(df)
df <- na.omit(df)
colnames(df)[colnames(df) == "market_value_in_eur"] <- "precio"

# Agregar nueva variable que tenga en cuenta los precios de los jugadores y poder hacer un split 
# test-train estratificado
caja_precios <- boxplot(df$precio)

df$precio_cat <- NA

for (i in 1:length(df$precio)) {
  if (df$precio[i] >= 100000000) { 
    df$precio_cat[i] <- "muy_alto"}
  if (df$precio[i] < 100000000 && df$precio[i] >= caja_precios$stats[5]) {
    df$precio_cat[i] <- "alto"}
  if (df$precio[i] < caja_precios$stats[5] && df$precio[i] >= caja_precios$stats[4]) {
    df$precio_cat[i] <- "medio"}
  if (df$precio[i] < caja_precios$stats[4] && df$precio[i] >= caja_precios$stats[3]) {
    df$precio_cat[i] <- "bajo"}
  if (df$precio[i] < caja_precios$stats[3]){
    df$precio_cat[i] <- "muy_bajo"}
}

# Suponiendo que `df$clase` es la variable categórica
set.seed(28749658)  # Fijar semilla para reproducibilidad

# Crear índices estratificados basados en la variable `clase`
train_indices <- createDataPartition(df$precio_cat, p = 0.7, list = FALSE)  # 70% entrenamiento

# Dividir los datos
train_data <- df[train_indices, ]
test_data <- df[-train_indices, ]

Verificar la distribución de clases

Datos de Entrenamiento

table(train_data$precio_cat)
## 
##     alto     bajo    medio muy_alto muy_bajo 
##      132      298      171       10      546

Datos de prueba

table(test_data$precio_cat)
## 
##     alto     bajo    medio muy_alto muy_bajo 
##       56      127       73        4      234

Análisis Exploratorio de Datos

Gráficos de barras de algunas variables categóricas

colnames(df)[colnames(df) == "market_value_in_eur"] <- "precio"
df$continente <- factor(df$continente, levels = c("europa", "america", "africa", "asia_oceania"))
grafico1 <- ggplot(df, aes(x=continente, fill = continente)) +
  geom_bar() +
  scale_fill_viridis(discrete = TRUE, option = "D") + # Paleta accesible
  labs(y = "Cantidad", 
       x = "Continente",
       title = "Cantidad de jugadores por continente") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle=45, vjust=1, hjust=1),
        legend.position = "none")

df$Comp <- factor(df$Comp, levels = c("es La Liga", "it Serie A", "fr Ligue 1", "de Bundesliga",
                                      "eng Premier League"))
grafico2 <- ggplot(df, aes(x=Comp, fill = Comp)) + 
  geom_bar() +
  scale_fill_viridis(discrete = TRUE, option = "D") + # Paleta accesible
  labs(y = "Cantidad", x = "Liga", title = "Cantidad de jugadores por liga") +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle=45, vjust=1, hjust=1),
        legend.position = "none")

posiciones <- table(df$position)
posiciones <- names(sort(posiciones))
df$position <- factor(df$position, levels = posiciones)
grafico3 <- ggplot(df, aes(x=position, fill = position)) + 
  geom_bar() +
  scale_fill_viridis(discrete = TRUE, option = "D") + # Paleta accesible
  labs(y = "Count", x = "Posiciones", title = "Cantidad de jugadores por posicion") +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle=45, vjust=1, hjust=1,),
        legend.position = "none")

grafico4 <- ggplot(df, aes(x=foot, fill = foot)) + 
  geom_bar() +
  scale_fill_viridis(discrete = TRUE, option = "D") + # Paleta accesible
  labs(y = "Count", x = "Pie hábil", title = "Cantidad de jugadores por pie habil") +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle=45, vjust=1, hjust=1,),
        legend.position = "none")

grid.arrange(grafico1, grafico2, grafico3, grafico4,nrow = 2)

Jugadores por continente

Como era de esperarse, predominan los jugadores europeos. Los americanos y africanos están casi en igual medida, siendo un poco mayor los americanos. Asia y Oceanía en conjunto aportan solo una mínima cantidad.

Cantidad de jugadores de liga

Todas las ligas están igual de representadas en el dataset, con lo cual el análisis del precio de jugadores según la liga será de interés.

Jugadores por posición

La mayor cantidad de jugadores son Defensores, y en menor medida los Mediocampistas y Delanteros, pero todas estas posiciones están uniformemente distribuidas. Como es de esperarse, los Arquero es la posición de menor representación.

Jugadores por pie hábil.

Como era de esperarse, mas de la mitad de los jugadores son derechos. Sin embargo, comparado con la proporción de personas diestras en el mundo (85%), la cantidad de jugadores zurdos es bastante mayor a estar proporción.

Correlograma

df %>% 
  dplyr::select(Age, Gls, Ast, precio) %>% 
  mutate(liga = df$current_club_domestic_competition_id) %>%
  ggpairs(., mapping = aes(colour = liga),
          upper = list(continuous = wrap("cor", size = 3, hjust=0.5)), progress=FALSE) + 
  scale_color_viridis_d(option = "D") + 
  scale_fill_viridis_d(option = "D") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1), legend.position = "bottom") + 
  theme_bw() +
  labs(title='Correlograma variables continuas')

Correlación entre Goles y Precio

Se observa una correlación positiva alta a moderada (Corr: 0.502). Esto sugiere que los jugadores que marcan más goles tienden a tener un valor de mercado más alto. La relación parece ser consistente a través de las diferentes ligas. Siendo menos pronunciada en las ligas española y francesa donde parecería que la cantida de goles anotados tiene menos importancia que en el resto de las ligas, y mas pronunciada en la liga inglesa donde parecería que la cantida de goles anotados tiene mas importancia que el resto de las ligas.

Correlación entre Asistencias y Precio

Existe una correlación positiva moderada (Corr: 0.465). Indica que los jugadores que dan más asistencias también tienden a tener un mayor valor en el mercado. La correlación es algo menor que con los goles, sugiriendo que el mercado valora más la capacidad goleadora. Se observa la misma tendencia que para la cantidad de goles, es menos pronunciada en las ligas española y francesa donde parecería que la cantida de asistencias tiene menos importancia que en el resto de las ligas, y mas pronunciada en la liga inglesa donde parecería que la cantida de asistencias tiene mas importancia que el resto de las ligas.

Correlación entre Edad y Precio

Se observa una correlación negativa baja pero clara (Corr: -0.176). Sugiere que el valor de mercado tiende a disminuir con la edad del jugador. Esto tiene sentido desde una perspectiva de inversión, ya que los jugadores más jóvenes tienen mayor potencial de desarrollo y años de carrera por delante. En este caso, la liga español tiene una pendiente mas negativa lo que podría evidenciar que los jugadores jovenes son mas valorados que el resto de las ligas, en cambio en la liga francesa se observa una pendiente mas suave con lo cual podría indicar que se valora mas la experiencia que el resto de las ligas.

Distribución por Ligas

Los diagramas de caja (boxplots) muestran diferencias en la distribución de precios entre ligas. Las ligas española y británica presentan valores más altos en general. Esto se alinea con el poder económico de estas ligas y específicamente de clubes como Real Madrid y Manchester City. La distribución de precios es notablemente asimétrica, con algunos valores muy altos que podrían considerarse outliers.

Correlaciones cruzadas

Existe una correlación positiva moderada entre goles y asistencias (Corr: 0.586). Esto sugiere que los jugadores más efectivos tienden a destacar tanto en goles como en asistencias. La edad muestra correlaciones muy débiles con goles y asistencias.

Jugadores mas caros

Los 10 jugadores mas caros

mas_caros_nombre <- df %>% 
  slice_max(order_by = precio, n=10)  %>% 
  pull(name)
mas_caros_equipo <- df %>% 
  slice_max(order_by = precio, n=10)  %>% 
  pull(Squad)
mas_caros_precio <- df %>% 
  slice_max(order_by = precio, n=10)  %>% 
  pull(precio)
mas_caro <- data.frame(Nombre = mas_caros_nombre, 
                       Equipo = mas_caros_equipo, 
                       Precio = mas_caros_precio)

kable(mas_caro)
Nombre Equipo Precio
erling haaland manchester city 2.0e+08
vinicius junior real madrid 2.0e+08
jude bellingham real madrid 1.8e+08
lamine yamal barcelona 1.5e+08
phil foden manchester city 1.5e+08
bukayo saka arsenal 1.4e+08
federico valverde real madrid 1.3e+08
florian wirtz leverkusen 1.3e+08
rodri manchester city 1.3e+08
declan rice arsenal 1.2e+08

Jugadores mas baratos

Los 10 jugadores mas baratos

mas_baratos_nombre <- df %>% 
  slice_min(order_by = precio, n=10)  %>% 
  pull(name)
mas_baratos_equipo <- df %>% 
  slice_min(order_by = precio, n=10)  %>% 
  pull(Squad)
mas_baratos_precio <- df %>% 
  slice_min(order_by = precio, n=10)  %>% 
  pull(precio)
mas_barato <- data.frame(Nombre = mas_baratos_nombre, 
                       Equipo = mas_baratos_equipo, 
                       Precio = mas_baratos_precio)

kable(mas_barato)
Nombre Equipo Precio
aurelien pelon lorient 50000
daouda traore nice 50000
sofiane sidi ali marseille 50000
steven baseya strasbourg 50000
yassin tallal getafe 50000
simone aresti cagliari 75000
adel mahamoud nantes 100000
antonio mirante milan 100000
daniele sommariva genoa 100000
dominic sadi bournemouth 100000
francesco rossi atalanta 100000
ichem ferrah lille 100000
ivan cuellar mallorca 100000
joel imasuen werder bremen 100000

Regresión Lineal Simple

Modelo “Goles son amores”

Precio = Goles

modelo_clasico_goles = lm(data = train_data, formula = precio ~ Gls)
summary(modelo_clasico_goles)
## 
## Call:
## lm(formula = precio ~ Gls, data = train_data)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -58318585  -5505828  -3505828   2261548 112681415 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5238452     519499   10.08   <2e-16 ***
## Gls          3267375     147163   22.20   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15400000 on 1155 degrees of freedom
## Multiple R-squared:  0.2991, Adjusted R-squared:  0.2985 
## F-statistic: 492.9 on 1 and 1155 DF,  p-value: < 2.2e-16
ggplot(df, aes(x= Gls, y=precio))+
  geom_point() +
  theme_bw() +
  geom_smooth(method = "lm", formula = y ~ x, color="forestgreen", se = FALSE)

Diagnóstico

datos_augmentados <- augment(modelo_clasico_goles)
g1 <- ggplot(datos_augmentados, aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept = 0) +
  geom_smooth(se = FALSE) +
  labs(title = "Residuos vs valores predichos") + 
  theme_bw()
g2 <- ggplot(datos_augmentados, aes(sample = .std.resid)) +
  stat_qq() +
  geom_abline() +
  labs(title = "Normal QQ plot") + 
  theme_bw()
g3 <- ggplot(datos_augmentados, aes(.fitted, sqrt(abs(.std.resid)))) +
  geom_point() +
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Scale-location plot")
g4 <- ggplot(datos_augmentados, aes(.hat, .std.resid)) +
  geom_vline(size = 2, colour = "white", xintercept = 0) +
  geom_hline(size = 2, colour = "white", yintercept = 0) +
  geom_point() + 
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Residual vs leverage")
grid.arrange(g1, g2, g3, g4, nrow=2)

Diagnóstico de Residuos

  • El gráfico de residuos vs valores predichos muestra un patrón de embudo, indicando heterocedasticidad
  • El QQ-plot muestra desviaciones significativas de la normalidad, especialmente en las colas
  • El Scale-location plot confirma la heterocedasticidad, con mayor variabilidad en los valores predichos más altos
  • El gráfico de leverage muestra varios puntos con alta influencia que podrían estar afectando el modelo

Interpretación del Modelo

  • El modelo establece una relación lineal simple entre el precio del jugador y los goles anotados
  • Por cada gol anotado, se precibe un aumento estadísticamente significativo (p-value < 0.05) en el precio del jugador de €3,267,375
  • El intercepto es €5,238,452, que representa el precio base estimado para un jugador sin goles
  • El modelo es estadísticamente significativo (p-value < 2.2e-16)
  • El R² ajustado es 0.2985, lo que indica que el modelo explica aproximadamente el 30% de la variabilidad en los precios

Evaluación del Rendimiento

Datos de entrenamiento
pred_modelo_clasico_goles <- augment(modelo_clasico_goles, newdata = train_data)
cat("RMSE: ", rmse(data = pred_modelo_clasico_goles, truth = precio, estimate = .fitted)$.estimate)
## RMSE:  15388866
cat("MAE: ", mae(data = pred_modelo_clasico_goles, truth = precio, estimate = .fitted)$.estimate)
## MAE:  9179813
Datos de prueba
pred_modelo_clasico_goles <- augment(modelo_clasico_goles, newdata = test_data)
cat("RMSE: ", rmse(data = pred_modelo_clasico_goles, truth = precio, estimate = .fitted)$.estimate)
## RMSE:  17503869
cat("MAE: ", mae(data = pred_modelo_clasico_goles, truth = precio, estimate = .fitted)$.estimate)
## MAE:  9732598
  • En datos de entrenamiento:
    • RMSE: 15,388,866
    • MAE: 9,179,813
  • En datos de prueba:
    • RMSE: 17,503,869
    • MAE: 9,732,598
  • La diferencia relativamente pequeña entre los errores de entrenamiento y prueba sugiere que el modelo no está sobreajustado

Limitaciones del Modelo

  • La relación lineal simple puede ser demasiado básica para capturar la complejidad del precio de los jugadores
  • La presencia de heterocedasticidad sugiere que la variabilidad del precio aumenta con el número de goles
  • El bajo R² indica que hay otros factores importantes que no están siendo considerados
  • Los supuestos de normalidad y homocedasticidad no se cumplen adecuadamente

Este modelo, aunque estadísticamente significativo, tiene limitaciones importantes para predecir el precio de los jugadores. La violación de los supuestos básicos y el bajo poder explicativo sugieren que se necesita un modelo más complejo que incorpore variables adicionales y posiblemente transformaciones de las variables existentes.

Modelo “La edad NO es lo de menos”

Precio = \(Edad\) + \(Edad^2\)

modelo_clasico_edad = modelo_clasico_edad2 = lm(data = train_data, formula = precio ~ Age + I(Age^2))
summary(modelo_clasico_edad)
## 
## Call:
## lm(formula = precio ~ Age + I(Age^2), data = train_data)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -13491197  -9734927  -5198104   2200547 186201791 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -45547305   13549572  -3.362 0.000801 ***
## Age           5113615    1054307   4.850 1.40e-06 ***
## I(Age^2)      -110147      20094  -5.482 5.18e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 17940000 on 1154 degrees of freedom
## Multiple R-squared:  0.05018,    Adjusted R-squared:  0.04853 
## F-statistic: 30.48 on 2 and 1154 DF,  p-value: 1.255e-13
ggplot(df, aes(x= Age, y=precio))+
  geom_point() +
  theme_bw() +
  geom_smooth(method = "lm", formula = y ~ x + I(x^2), color="forestgreen", se = FALSE)

cat("A partir de los", round(5113615/(2*110147),0), "años, el precio de los jugadores comienza a disminuir")
## A partir de los 23 años, el precio de los jugadores comienza a disminuir

Diagnóstico

datos_augmentados <- augment(modelo_clasico_edad)
g1 <- ggplot(datos_augmentados, aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept = 0) +
  geom_smooth(se = FALSE) +
  labs(title = "Residuos vs valores predichos") + 
  theme_bw()
g2 <- ggplot(datos_augmentados, aes(sample = .std.resid)) +
  stat_qq() +
  geom_abline() +
  labs(title = "Normal QQ plot") + 
  theme_bw()
g3 <- ggplot(datos_augmentados, aes(.fitted, sqrt(abs(.std.resid)))) +
  geom_point() +
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Scale-location plot")
g4 <- ggplot(datos_augmentados, aes(.hat, .std.resid)) +
  geom_vline(size = 2, colour = "white", xintercept = 0) +
  geom_hline(size = 2, colour = "white", yintercept = 0) +
  geom_point() + 
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Residual vs leverage")
grid.arrange(g1, g2, g3, g4, nrow=2)

Diagnóstico de Residuos

  • El gráfico de residuos vs valores predichos muestra un patrón de embudo similar al modelo anterior
  • El QQ-plot muestra desviaciones significativas de la normalidad, especialmente en las colas superiores
  • El Scale-location plot indica heterocedasticidad
  • El gráfico de leverage muestra algunos puntos influyentes, aunque menos pronunciados que en el modelo de goles

Interpretación del Modelo

  • El modelo establece una relación cuadrática entre el precio del jugador y su edad
  • El precio aumenta con la edad hasta los 23 años, punto a partir del cual comienza a disminuir
  • Los coeficientes son:
    • Edad: +5,113,615 (efecto lineal positivo)
    • Edad²: -110,147 (efecto cuadrático negativo)
    • Intercepto: -45,547,305
  • El modelo es estadísticamente significativo (p-value: 1.255e-13)
  • El R² ajustado es 0.04853, lo que indica que el modelo explica solo aproximadamente el 5% de la variabilidad en los precios

Evaluación del Rendimiento

Datos de entrenamiento
pred_modelo_clasico_edad <- augment(modelo_clasico_edad, newdata = train_data)
cat("RMSE: ", rmse(data = pred_modelo_clasico_edad, truth = precio, estimate = .fitted)$.estimate)
## RMSE:  17914627
cat("MAE: ", mae(data = pred_modelo_clasico_edad, truth = precio, estimate = .fitted)$.estimate)
## MAE:  10519126
Datos de prueba
pred_modelo_clasico_edad <- augment(modelo_clasico_edad, newdata = test_data)
cat("RMSE: ", rmse(data = pred_modelo_clasico_edad, truth = precio, estimate = .fitted)$.estimate)
## RMSE:  18482683
cat("MAE: ", mae(data = pred_modelo_clasico_edad, truth = precio, estimate = .fitted)$.estimate)
## MAE:  10655848
  • En datos de entrenamiento:
    • RMSE: 17,914,627
    • MAE: 10,519,126
  • En datos de prueba:
    • RMSE: 18,482,683
    • MAE: 10,655,848
  • Los errores son mayores que en el modelo de goles, sugiriendo que la edad por sí sola es un predictor más débil del precio

Limitaciones del Modelo

  • El muy bajo R² sugiere que la edad por sí sola no es un buen predictor del precio
  • La relación cuadrática captura el hecho de que los jugadores alcanzan un pico de valor, pero el ajuste general es pobre
  • Los supuestos de normalidad y homocedasticidad siguen sin cumplirse
  • El modelo no captura otros factores importantes que afectan el precio

Este modelo confirma que existe una relación no lineal entre la edad y el precio de los jugadores, con un punto máximo alrededor de los 23 años. Sin embargo, su bajo poder explicativo sugiere que la edad debe combinarse con otras variables para obtener predicciones más precisas.

Regresión Lineal Múltiple

Modelo “Ahora va en serio”

\(Precio\) = \(Goles\) + \(Edad\) + \(Edad^2\) + \(Asistencias\) + \(Continente\) \(de\) \(nacimiento\) \(del\) \(jugador\) + \(Liga\) \(donde\) \(juega\)

modelo_clasico_multiple_1 = lm(data = train_data, 
                             formula = precio ~ Gls + Age + I(Age^2) + Ast + 
                               continente + current_club_domestic_competition_id)
summary(modelo_clasico_multiple_1)
## 
## Call:
## lm(formula = precio ~ Gls + Age + I(Age^2) + Ast + continente + 
##     current_club_domestic_competition_id, data = train_data)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -37702078  -5979937  -1352304   4179402 117568853 
## 
## Coefficients:
##                                         Estimate Std. Error t value Pr(>|t|)
## (Intercept)                             -8999579   10503367  -0.857 0.391719
## Gls                                      2189855     160682  13.629  < 2e-16
## Age                                      1607300     804050   1.999 0.045844
## I(Age^2)                                  -44966      15312  -2.937 0.003384
## Ast                                      2152607     244624   8.800  < 2e-16
## continenteamerica                        4494113    1848911   2.431 0.015223
## continenteasia_oceania                   2283362    3956794   0.577 0.564003
## continenteeuropa                         2748364    1459721   1.883 0.059981
## current_club_domestic_competition_idFR1 -4436917    1268565  -3.498 0.000487
## current_club_domestic_competition_idGB1 11060116    1286087   8.600  < 2e-16
## current_club_domestic_competition_idIT1 -2429060    1229943  -1.975 0.048515
## current_club_domestic_competition_idL1  -4011806    1265818  -3.169 0.001568
##                                            
## (Intercept)                                
## Gls                                     ***
## Age                                     *  
## I(Age^2)                                ** 
## Ast                                     ***
## continenteamerica                       *  
## continenteasia_oceania                     
## continenteeuropa                        .  
## current_club_domestic_competition_idFR1 ***
## current_club_domestic_competition_idGB1 ***
## current_club_domestic_competition_idIT1 *  
## current_club_domestic_competition_idL1  ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 13350000 on 1145 degrees of freedom
## Multiple R-squared:  0.4783, Adjusted R-squared:  0.4733 
## F-statistic: 95.43 on 11 and 1145 DF,  p-value: < 2.2e-16

Diagnóstico

datos_augmentados <- augment(modelo_clasico_multiple_1)
g5 <- ggplot(datos_augmentados, aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept = 0) +
  geom_smooth(se = FALSE) +
  labs(title = "Residuos vs valores predichos") + 
  theme_bw()
g6 <- ggplot(datos_augmentados, aes(sample = .std.resid)) +
  stat_qq() +
  geom_abline() +
  labs(title = "Normal QQ plot") + 
  theme_bw()
g7 <- ggplot(datos_augmentados, aes(.fitted, sqrt(abs(.std.resid)))) +
  geom_point() +
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Scale-location plot")
g8 <- ggplot(datos_augmentados, aes(.hat, .std.resid)) +
  geom_vline(size = 2, colour = "white", xintercept = 0) +
  geom_hline(size = 2, colour = "white", yintercept = 0) +
  geom_point() + 
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Residual vs leverage")
grid.arrange(g5, g6, g7, g8, nrow=2)

Diagnóstico de Residuos

  • El gráfico de residuos vs valores predichos muestra una mejora en el patrón de heterocedasticidad
  • El QQ-plot indica una mejor aproximación a la normalidad en el centro de la distribución
  • El Scale-location plot muestra una variabilidad más estable que los modelos anteriores
  • El gráfico de leverage identifica menos puntos influyentes extremos

Interpretación del Modelo

  • El modelo incorpora múltiples variables predictoras: goles, edad (lineal y cuadrática), asistencias, continente de origen y liga
  • Los coeficientes más significativos son:
    • Goles: +2,189,855 por gol (p-value < 2e-16). Esto indica que el precio de los jugadores que tienen misma cantidad de asistencias y edad aumenta €2,189,855 por cada gol anotado
    • Asistencias: +2,152,607 por asistencia (p-value < 2e-16). Esto indica que el precio de los jugadores que tienen misma cantidad de goles y edad aumenta €2,152,607 por cada asistencia adicional.
    • Edad: efecto cuadrático con máximo alrededor de los 23 años
    • América: +4,494,113 respecto a África (p-value = 0.015)
    • Premier League: +11,060,116 respecto a la liga de referencia, La Liga (España) (p-value < 2e-16)
    • Ligue 1: -4,436,917 respecto a la liga de referencia, La Liga (p-value < 0.05)
    • Bundesliga: -4,011,806 respecto a la liga de referencia, La Liga (p-value < 0.05)
    • Serie A (Italia): -2,429,060 respecto a la liga de referencia, La Liga (p-value < 0.05)
  • El R² ajustado es 0.4733, indicando que el modelo explica aproximadamente el 47% de la variabilidad

Evaluación del Rendimiento

Datos de entrenamiento
pred_modelo_clasico_multiple_1 <- augment(modelo_clasico_multiple_1, newdata = train_data)
cat("RMSE: ", rmse(data = pred_modelo_clasico_multiple_1, truth = precio, estimate = .fitted)$.estimate)
## RMSE:  13276913
cat("MAE: ", mae(data = pred_modelo_clasico_multiple_1, truth = precio, estimate = .fitted)$.estimate)
## MAE:  8093280
Datos de prueba
pred_modelo_clasico_multiple_1 <- augment(modelo_clasico_multiple_1, newdata = test_data)
cat("RMSE: ", rmse(data = pred_modelo_clasico_multiple_1, truth = precio, estimate = .fitted)$.estimate)
## RMSE:  15450180
cat("MAE: ", mae(data = pred_modelo_clasico_multiple_1, truth = precio, estimate = .fitted)$.estimate)
## MAE:  8469906
  • En datos de entrenamiento:
    • RMSE: 13,276,913
    • MAE: 8,093,280
  • En datos de prueba:
    • RMSE: 15,450,180
    • MAE: 8,469,906
  • La diferencia moderada entre errores de entrenamiento y prueba sugiere un nivel aceptable de generalización

Mejoras Respecto a Modelos Anteriores

  • El R² ajustado aumentó significativamente (de 0.30 y 0.05 a 0.47)
  • Los errores de predicción (RMSE y MAE) disminuyeron
  • Los diagnósticos de residuos muestran mejores propiedades estadísticas
  • La incorporación de variables categóricas captura efectos específicos por continente y liga

Este modelo representa una mejora sustancial sobre los modelos simples anteriores, capturando efectos más complejos y reduciendo los errores de predicción. Sin embargo, aún hay espacio para mejoras, especialmente en el tratamiento de valores extremos y la posible incorporación de más variables relevantes.

Modelo “Ahora va en serio 2”

\(log(Precio)\) = \(Goles\) + \(Edad\) + \(Edad^2\) + \(Asistencias\) + \(Continente\) \(de\) \(nacimiento\) \(del\) \(jugador\) + \(Liga\) \(donde\) \(juega\)

Al utilizar una transformación logarítmica, la interpretación del modelo es diferente, ya que se introduce una relación de semielasticidad entre las variables. La variación de la variable a predecir es en términos porcentuales según aumenta en una unidad la variable predictora numérica o según la variable de referencia categórica.

Por lo tanto:

\[ c = (\exp(b) - 1) \times 100 \quad \text{(13)} \]

Donde

\[ y = a + b \times x \quad \text{(14)} \]

En este caso, si \(x\) es una variable numérica entonces \(y\) varía \(c\)% por cada aumento de \(x\) en una unidad. Si en cambio \(x\) es una variable categórica, \(y\) varía \(c\)% con respecto a la variable categórica de referencia.

modelo_clasico_multiple = lm(data = train_data, 
                                formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + 
                                  continente + current_club_domestic_competition_id)
summary(modelo_clasico_multiple)
## 
## Call:
## lm(formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + continente + 
##     current_club_domestic_competition_id, data = train_data)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.0055 -0.6931  0.0515  0.7146  3.5105 
## 
## Coefficients:
##                                          Estimate Std. Error t value Pr(>|t|)
## (Intercept)                              5.573579   0.844203   6.602 6.19e-11
## Gls                                      0.116194   0.012915   8.997  < 2e-16
## Age                                      0.787591   0.064625  12.187  < 2e-16
## I(Age^2)                                -0.016285   0.001231 -13.232  < 2e-16
## Ast                                      0.172888   0.019662   8.793  < 2e-16
## continenteamerica                        0.404139   0.148605   2.720  0.00664
## continenteasia_oceania                   0.007232   0.318025   0.023  0.98186
## continenteeuropa                         0.041694   0.117324   0.355  0.72237
## current_club_domestic_competition_idFR1 -0.296188   0.101960  -2.905  0.00374
## current_club_domestic_competition_idGB1  0.817675   0.103369   7.910 6.02e-15
## current_club_domestic_competition_idIT1 -0.027229   0.098856  -0.275  0.78303
## current_club_domestic_competition_idL1  -0.285435   0.101740  -2.806  0.00511
##                                            
## (Intercept)                             ***
## Gls                                     ***
## Age                                     ***
## I(Age^2)                                ***
## Ast                                     ***
## continenteamerica                       ** 
## continenteasia_oceania                     
## continenteeuropa                           
## current_club_domestic_competition_idFR1 ** 
## current_club_domestic_competition_idGB1 ***
## current_club_domestic_competition_idIT1    
## current_club_domestic_competition_idL1  ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.073 on 1145 degrees of freedom
## Multiple R-squared:  0.467,  Adjusted R-squared:  0.4619 
## F-statistic: 91.21 on 11 and 1145 DF,  p-value: < 2.2e-16

Diagnóstico

Los siguientes histogramas muestran como mejora la ditribución de los precios aplicando la distribución lograrítmica logrando una aproximación a los normalidad y homocedasticidad de los datos a predecir.

hist1 <- ggplot(df, aes(x=precio))+
  geom_histogram() +
  labs(title = "Histograma del Precio") + 
  theme_bw()
hist2 <- ggplot(df, aes(x=log(precio)))+
  geom_histogram() +
  labs(title = "Histograma del log(Precio)") + 
  theme_bw()
grid.arrange( hist1, hist2, nrow=1)

En este caso, el valor de \(R^2\) ajustado del modelo es con respecto a la variable transformada, por lo tanto tenemos que predecir todos los datos utilizados en el modelo y trasformarlos exponencialmente para obtener el valor real del salario por hora esperado.

Finalmente, se calcula el valor de \(R^2\) ajustado “manualmente”.

pred_modelo_clasico_multiple <- augment(modelo_clasico_multiple, newdata = train_data)
pred_modelo_clasico_multiple$exp_fitted <- exp(pred_modelo_clasico_multiple$.fitted)
metricas1 = metrics(data = pred_modelo_clasico_multiple,
                    truth = precio, estimate = exp_fitted) %>% 
  mutate(.estimate = round(.estimate, 4))
#rcuadrado <- metricas1$.estimate[2]
#cat("R-cuadrado = ", rcuadrado)
#metricas1
# Calcular el R² ajustado manualmente
n <- nrow(pred_modelo_clasico_multiple)  # Número de observaciones
p <- length(coef(modelo_clasico_multiple)) - 1  # Número de predictores (restamos 1 por el intercepto)

r2 <- metricas1 %>% filter(.metric == "rsq") %>% pull(.estimate)
r2_ajustado <- 1 - ((1 - r2) * (n - 1) / (n - p - 1))

# Mostrar el R² ajustado
cat("R^2 ajustado = ", r2_ajustado)
## R^2 ajustado =  0.2908521
datos_augmentados <- augment(modelo_clasico_multiple)
g9 <- ggplot(datos_augmentados, aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept = 0) +
  geom_smooth(se = FALSE) +
  labs(title = "Residuos vs valores predichos") + 
  theme_bw()
g10 <- ggplot(datos_augmentados, aes(sample = .std.resid)) +
  stat_qq() +
  geom_abline() +
  labs(title = "Normal QQ plot") + 
  theme_bw()
g11 <- ggplot(datos_augmentados, aes(.fitted, sqrt(abs(.std.resid)))) +
  geom_point() +
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Scale-location plot")
g12 <- ggplot(datos_augmentados, aes(.hat, .std.resid)) +
  geom_vline(size = 2, colour = "white", xintercept = 0) +
  geom_hline(size = 2, colour = "white", yintercept = 0) +
  geom_point() + 
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Residual vs leverage")
grid.arrange(g9, g10, g11, g12, nrow=2)

Diagnóstico de Residuos

  • El gráfico de residuos vs valores predichos muestra una distribución más homogénea
  • El QQ-plot indica una notable mejora en la normalidad de los residuos
  • El Scale-location plot muestra una varianza más estable
  • El gráfico de leverage sugiere menos influencia de valores extremos

Interpretación del Modelo

  • El modelo utiliza la transformación logarítmica de la variable precio y mantiene las mismas variables predictoras
  • Los coeficientes más significativos son:
    • Goles: +0.116 (p-value < 2e-16).Esto indica que el precio de los jugadores que tienen misma cantidad de asistencias y edad aumenta un 12.3% por cada gol anotado.
    • Asistencias: +0.173 (p-value < 2e-16). Esto indica que el precio de los jugadores que tienen misma cantidad de goles y edad aumenta un 18.9% por cada asistencia adicional.
    • Edad: efecto cuadrático significativo (p-value < 2e-16)
    • América: +0.404 respecto a África (p-value = 0.007), indica un aumento estadísticamete significativo de 49.8% en el precio de los jugadores americanos con respecto a los africanos.
    • Premier League: +0.818 respecto a la liga de referencia, (p-value < 2e-16) indica un aumento estadísticamente significativo de 126.5% en el precio de los jugadores de la Premier League con respecto a los jugadores de La Liga (España)
    • Ligue 1 (Francia): -0.296 respecto a la liga de referencia, (p-value = 0.003) indica una disminución estadísticamente significativa de 25.6% en el precio de los jugadores de la Ligue 1 con respecto a los jugadores de La Liga.
    • Bundesliga: -0.285 respecto de la liga de referencia (p-value = 0.005) indica una disminución estadísticamente significativa de 24.8% en el preicio de los jugadores de la Bundesliga con respecto a La Liga.
  • El R² ajustado es 0.29, menor al modelo anterior pero con mejor interpretabilidad y diagnóstico.

Evaluación del Rendimiento

Datos de entrenamiento
cat("RMSE: ", rmse(data = pred_modelo_clasico_multiple, truth = precio, estimate = exp_fitted)$.estimate)
## RMSE:  28500598
cat("MAE: ", mae(data = pred_modelo_clasico_multiple, truth = precio, estimate = exp_fitted)$.estimate)
## MAE:  8057781
Datos de prueba
pred_modelo_clasico_multiple <- augment(modelo_clasico_multiple, newdata = test_data)
pred_modelo_clasico_multiple$exp_fitted <- exp(pred_modelo_clasico_multiple$.fitted)
cat("RMSE: ", rmse(data = pred_modelo_clasico_multiple, truth = precio, estimate = exp_fitted)$.estimate)
## RMSE:  17508456
cat("MAE: ", mae(data = pred_modelo_clasico_multiple, truth = precio, estimate = exp_fitted)$.estimate)
## MAE:  7357616
  • En datos de entrenamiento:
    • RMSE: 28,500,598
    • MAE: 8,057,781
  • En datos de prueba:
    • RMSE: 17,508,456
    • MAE: 7,357,616
  • La transformación logarítmica mejora el MAE en los datos de prueba, aunque el RMSE es más alto

Mejoras Respecto al Modelo Anterior

  • Mejor interpretabilidad de los coeficientes en términos de porcentajes
  • Mejor cumplimiento de los supuestos de normalidad y homocedasticidad
  • Reducción del MAE en datos de prueba
  • Mayor estabilidad en la predicción de valores extremos

La transformación logarítmica del precio mejora las propiedades estadísticas del modelo y facilita la interpretación de los efectos. Este modelo parece más adecuado para predecir el precio de los jugadores, especialmente cuando se considera la interpretabilidad y la estabilidad de las predicciones.

Regresión Lineal Múltiple Robusta

Modelo “Goles son amores ROBUSTOS”

Precio = Goles

Se utiliza el modelo base de Robustbase, donde la fución de pérdida utilizada es la bicuadrada y utiliza el estimador MM.

modelo_lmrob_goles <- lmrob(formula = precio ~ Gls, data=train_data)
pred_modelo_lmrob_goles <- data.frame(
  Gls = train_data$Gls,
  precio_pred = predict(modelo_lmrob_goles, newdata = train_data)
)
summary(modelo_lmrob_goles)
## 
## Call:
## lmrob(formula = precio ~ Gls, data = train_data)
##  \--> method = "MM"
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -11398849  -2040823    -40823   7178107 172914731 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  3040823     233384   13.03  < 2e-16 ***
## Gls           890535     123689    7.20 1.08e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Robust residual standard error: 3632000 
## Multiple R-squared:  0.2183, Adjusted R-squared:  0.2177 
## Convergence in 23 IRWLS iterations
## 
## Robustness weights: 
##  158 observations c(3,19,23,52,55,58,67,76,80,83,89,92,105,112,130,132,141,143,144,156,161,163,164,167,172,173,175,178,190,200,204,212,216,218,226,232,248,253,256,262,265,271,283,287,288,289,290,292,301,309,310,316,319,321,322,327,333,338,339,341,354,366,367,368,372,384,385,388,390,407,433,461,463,474,476,482,484,488,489,500,501,510,526,537,543,563,564,565,575,578,584,586,593,597,602,608,630,634,640,646,662,666,674,691,695,696,705,713,736,753,754,760,764,774,793,799,800,801,806,811,813,823,833,853,857,867,870,871,875,901,913,917,918,929,934,953,954,962,963,965,968,973,982,1024,1036,1052,1053,1063,1070,1091,1096,1100,1123,1124,1132,1134,1138,1154)
##   are outliers with |weight| <= 4.7e-05 ( < 8.6e-05); 
##  44 weights are ~= 1. The remaining 955 ones are summarized as
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 0.002176 0.877400 0.959300 0.855000 0.983700 0.999000 
## Algorithmic parameters: 
##        tuning.chi                bb        tuning.psi        refine.tol 
##         1.548e+00         5.000e-01         4.685e+00         1.000e-07 
##           rel.tol         scale.tol         solve.tol          zero.tol 
##         1.000e-07         1.000e-10         1.000e-07         1.000e-10 
##       eps.outlier             eps.x warn.limit.reject warn.limit.meanrw 
##         8.643e-05         4.911e-11         5.000e-01         5.000e-01 
##      nResample         max.it       best.r.s       k.fast.s          k.max 
##            500             50              2              1            200 
##    maxit.scale      trace.lev            mts     compute.rd fast.s.large.n 
##            200              0           1000              0           2000 
##                   psi           subsampling                   cov 
##            "bisquare"         "nonsingular"         ".vcov.avar1" 
## compute.outlier.stats 
##                  "SM" 
## seed : int(0)

El siguiente gráfico muestra la linea de regresión del modelo lineal clásico (violeta) frente a la regresión del modelo robusto (amarillo).

Se puede apreciar muy claramente como el modelo robusto tiene una pendiente mas baja ajustando en menor medida a los jugadores de mayor cotización.

ggplot(train_data, aes(x = Gls, y = precio)) +
  geom_point() +
  geom_smooth(method = "lm", formula = y ~ x, color="darkviolet", se = FALSE) +
  geom_line(data = pred_modelo_lmrob_goles, aes(x = Gls, y = precio_pred), color = "yellow") +
  theme_bw()

Interpretación del Modelo

  • El modelo establece una relación lineal simple entre el precio del jugador y los goles anotados
  • Por cada gol anotado, el precio del jugador aumenta en €890,535; mucho menor al estimado por el método clásico de €3,267,375
  • El intercepto es €3,040,824, que representa el precio base estimado para un jugador sin goles también presente una notable baja frente al del modelo clásico de €5,238,452
  • El R² ajustado es 0.2177, lo que indica que el modelo explica aproximadamente el 21.8% de la variabilidad en los precios, observando una dismunición considerable frente al 30% del modelo clásico.

Evaluación y comparación de modelos frente a datos de prueba

modelos_simples <- list(simple_1 = modelo_clasico_goles, 
                simple_2 = modelo_lmrob_goles)

lista_predicciones_testing = map(.x = modelos_simples, .f = augment, newdata = test_data) 


goles_clasico_test = lista_predicciones_testing$simple_1 %>%  
  metrics(truth=precio, estimate=.fitted) %>%
  mutate(.estimate=round(.estimate, 4))

goles_robusto_test = lista_predicciones_testing$simple_2 %>%  
  metrics(truth=precio, estimate=.fitted) %>%
  mutate(.estimate=round(.estimate, 4))


metrica_goles <- rbind(goles_clasico_test[c(1,3),], goles_robusto_test[c(1,3),])

modelitos_simples <- c(rep("Clasico - Goles",2),rep("Robusto - Goles",2))
metricas <- cbind(modelitos_simples, metrica_goles)
kable(metricas)
modelitos_simples .metric .estimator .estimate
Clasico - Goles rmse standard 17503869
Clasico - Goles mae standard 9732598
Robusto - Goles rmse standard 19142968
Robusto - Goles mae standard 8713804
  • El RMSE es menor para el modelo clásico, sin embargo, al estar trabajando con datos que tienen outliers lo correcto es compararlo contra los valores de MAE.
  • El MAE es menor para el modelo robusto, mostrando un mejor desempeño frente al modelo clásico.

Primer Modelo Multiple Robusto

\(Precio\) = \(Goles\) + \(Edad\) + \(Edad^2\) + \(Asistencias\) + \(Continente\) \(de\) \(nacimiento\) \(del\) \(jugador\) + \(Liga\) \(donde\) \(juega\)

modelo_multiple_lmrob <- lmrob(formula = precio ~ Gls + Age + I(Age^2) + Ast + 
                                continente + current_club_domestic_competition_id, 
                                data=train_data)
summary(modelo_multiple_lmrob)
## 
## Call:
## lmrob(formula = precio ~ Gls + Age + I(Age^2) + Ast + continente + current_club_domestic_competition_id, 
##     data = train_data)
##  \--> method = "MM"
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -38166107  -1948976    313839   5696751 174801160 
## 
## Coefficients:
##                                          Estimate Std. Error t value Pr(>|t|)
## (Intercept)                             -13710204    3541492  -3.871 0.000114
## Gls                                        526022      90783   5.794 8.86e-09
## Age                                       1499374     264013   5.679 1.71e-08
## I(Age^2)                                   -31880       4959  -6.429 1.89e-10
## Ast                                        833746     130969   6.366 2.80e-10
## continenteamerica                          600051     661817   0.907 0.364771
## continenteasia_oceania                   32964436    5364894   6.144 1.11e-09
## continenteeuropa                          -373158     466154  -0.801 0.423585
## current_club_domestic_competition_idFR1   -491839     341530  -1.440 0.150112
## current_club_domestic_competition_idGB1   3289984     932242   3.529 0.000434
## current_club_domestic_competition_idIT1    -55126     348407  -0.158 0.874309
## current_club_domestic_competition_idL1    -478953     324300  -1.477 0.139983
##                                            
## (Intercept)                             ***
## Gls                                     ***
## Age                                     ***
## I(Age^2)                                ***
## Ast                                     ***
## continenteamerica                          
## continenteasia_oceania                  ***
## continenteeuropa                           
## current_club_domestic_competition_idFR1    
## current_club_domestic_competition_idGB1 ***
## current_club_domestic_competition_idIT1    
## current_club_domestic_competition_idL1     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Robust residual standard error: 3425000 
## Multiple R-squared:  0.4751, Adjusted R-squared:  0.4701 
## Convergence in 24 IRWLS iterations
## 
## Robustness weights: 
##  149 observations c(3,19,23,52,58,67,76,80,83,89,92,112,130,132,143,156,161,163,164,167,172,173,190,200,204,212,216,218,221,226,248,253,256,262,265,271,283,287,288,289,290,292,301,309,310,316,319,321,322,327,333,338,339,341,354,366,367,368,372,374,384,385,388,390,403,407,433,461,463,476,482,489,500,501,510,526,537,543,563,564,565,574,575,578,586,590,593,597,608,630,634,640,644,662,666,674,691,696,705,713,736,746,753,754,760,774,793,799,800,801,811,813,823,833,853,867,870,871,875,901,913,917,918,929,934,953,954,956,962,963,965,968,973,975,982,1019,1024,1036,1054,1063,1070,1091,1096,1123,1124,1130,1134,1138,1154)
##   are outliers with |weight| = 0 ( < 8.6e-05); 
##  72 weights are ~= 1. The remaining 936 ones are summarized as
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 0.0002669 0.8555000 0.9522000 0.8486000 0.9836000 0.9990000 
## Algorithmic parameters: 
##        tuning.chi                bb        tuning.psi        refine.tol 
##         1.548e+00         5.000e-01         4.685e+00         1.000e-07 
##           rel.tol         scale.tol         solve.tol          zero.tol 
##         1.000e-07         1.000e-10         1.000e-07         1.000e-10 
##       eps.outlier             eps.x warn.limit.reject warn.limit.meanrw 
##         8.643e-05         2.910e-09         5.000e-01         5.000e-01 
##      nResample         max.it       best.r.s       k.fast.s          k.max 
##            500             50              2              1            200 
##    maxit.scale      trace.lev            mts     compute.rd fast.s.large.n 
##            200              0           1000              0           2000 
##                   psi           subsampling                   cov 
##            "bisquare"         "nonsingular"         ".vcov.avar1" 
## compute.outlier.stats 
##                  "SM" 
## seed : int(0)

Interpretación del Modelo

  • El modelo incorpora múltiples variables predictoras: goles, edad (lineal y cuadrática), asistencias, continente de origen y liga
  • Los coeficientes más significativos son:
    • Goles: +545,409 por gol (p-value = 9.71e-09). Esto indica que el precio de los jugadores que tienen misma cantidad de asistencias y edad aumenta €€545,409 por cada gol anotado
    • Asistencias: +814,283 por asistencia (p-value = 2.51e-10). Esto indica que el precio de los jugadores que tienen misma cantidad de goles y edad aumenta €814,283 por cada asistencia adicional.
    • Edad: efecto cuadrático
    • La variable continente ya no es significativa a la hora de explicar el precio de los jugadores, pero se observa como se invierte la relación con los jugadores europeas donde los africanos son mas valorados en este modelo que en el simple.
    • Premier League: +3,358,109 respecto a la liga de referencia, La Liga (España) (p-value = 0.000357). Los jugadores de la liga inglesa siguen siendo los mas valorados pero en menor medida que el modelo simple.
    • El resto de las ligas dejan de ser significativas, pero se mantiene la tendencia que estos jugadores son menor valorados que los de la liga española.
  • El R² ajustado es 0.3699, indicando que el modelo explica aproximadamente el 37% de la variabilidad, disminuyendo con respecto al 47% del modelo simple

Evaluación y comparación de modelos frente a datos de prueba

modelos_comparacion <- list(multiple_1 = modelo_clasico_multiple_1,
                robusto_1 = modelo_multiple_lmrob)

lista_predicciones_testing = map(.x = modelos_comparacion, .f = augment, newdata = test_data) 


metricas_clasico_test = lista_predicciones_testing$multiple_1 %>% 
  metrics(truth=precio, estimate=.fitted) %>%
  mutate(.estimate=round(.estimate, 4))

metricas_robusto_test = lista_predicciones_testing$robusto_1 %>%
  metrics(truth=precio, estimate=.fitted) %>%
  mutate(.estimate=round(.estimate, 4))


metrica <- rbind(metricas_clasico_test[c(1,3),], metricas_robusto_test[c(1,3),])

modelitos_comparacion <- c(rep("Multiple - Precio",2),
               rep("Robusto - Precio - psi = bisquare",2))
metricas <- cbind(modelitos_comparacion, metrica)
kable(metricas)
modelitos_comparacion .metric .estimator .estimate
Multiple - Precio rmse standard 15450180
Multiple - Precio mae standard 8469906
Robusto - Precio - psi = bisquare rmse standard 18164916
Robusto - Precio - psi = bisquare mae standard 7909289
  • El RMSE es menor para el modelo clásico, sin embargo, al estar trabajando con datos que tienen outliers lo correcto es compararlo contra los valores de MAE.
  • El MAE es menor para el modelo robusto, mostrando un mejor desempeño frente al modelo clásico.

Segundo Modelo Multiple Robusto

\(log(Precio)\) = \(Goles\) + \(Edad\) + \(Edad^2\) + \(Asistencias\) + \(Continente\) \(de\) \(nacimiento\) \(del\) \(jugador\) + \(Liga\) \(donde\) \(juega\)

modelo_multiple_lmrob_2 <- lmrob(formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + 
                                 continente + current_club_domestic_competition_id, 
                               data=train_data)
summary(modelo_multiple_lmrob_2)
## 
## Call:
## lmrob(formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + continente + current_club_domestic_competition_id, 
##     data = train_data)
##  \--> method = "MM"
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -4.15651 -0.71534  0.01675  0.68707  3.45838 
## 
## Coefficients:
##                                          Estimate Std. Error t value Pr(>|t|)
## (Intercept)                              5.796426   1.235981   4.690 3.06e-06
## Gls                                      0.111085   0.010745  10.339  < 2e-16
## Age                                      0.772800   0.090728   8.518  < 2e-16
## I(Age^2)                                -0.016112   0.001655  -9.734  < 2e-16
## Ast                                      0.170219   0.014713  11.569  < 2e-16
## continenteamerica                        0.439570   0.130353   3.372 0.000771
## continenteasia_oceania                   0.065008   0.310255   0.210 0.834070
## continenteeuropa                         0.089042   0.106069   0.839 0.401378
## current_club_domestic_competition_idFR1 -0.231087   0.106080  -2.178 0.029578
## current_club_domestic_competition_idGB1  0.917136   0.110887   8.271 3.66e-16
## current_club_domestic_competition_idIT1 -0.014964   0.101669  -0.147 0.883015
## current_club_domestic_competition_idL1  -0.282461   0.098817  -2.858 0.004335
##                                            
## (Intercept)                             ***
## Gls                                     ***
## Age                                     ***
## I(Age^2)                                ***
## Ast                                     ***
## continenteamerica                       ***
## continenteasia_oceania                     
## continenteeuropa                           
## current_club_domestic_competition_idFR1 *  
## current_club_domestic_competition_idGB1 ***
## current_club_domestic_competition_idIT1    
## current_club_domestic_competition_idL1  ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Robust residual standard error: 1.024 
## Multiple R-squared:  0.4842, Adjusted R-squared:  0.4793 
## Convergence in 17 IRWLS iterations
## 
## Robustness weights: 
##  101 weights are ~= 1. The remaining 1056 ones are summarized as
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.06249 0.86890 0.94640 0.90070 0.98330 0.99900 
## Algorithmic parameters: 
##        tuning.chi                bb        tuning.psi        refine.tol 
##         1.548e+00         5.000e-01         4.685e+00         1.000e-07 
##           rel.tol         scale.tol         solve.tol          zero.tol 
##         1.000e-07         1.000e-10         1.000e-07         1.000e-10 
##       eps.outlier             eps.x warn.limit.reject warn.limit.meanrw 
##         8.643e-05         2.910e-09         5.000e-01         5.000e-01 
##      nResample         max.it       best.r.s       k.fast.s          k.max 
##            500             50              2              1            200 
##    maxit.scale      trace.lev            mts     compute.rd fast.s.large.n 
##            200              0           1000              0           2000 
##                   psi           subsampling                   cov 
##            "bisquare"         "nonsingular"         ".vcov.avar1" 
## compute.outlier.stats 
##                  "SM" 
## seed : int(0)
pred_modelo_robusto_multiple <- augment(modelo_multiple_lmrob_2, newdata = train_data)
pred_modelo_robusto_multiple$exp_fitted <- exp(pred_modelo_robusto_multiple$.fitted)
metricas2 = metrics(data = pred_modelo_robusto_multiple,
                    truth = precio, estimate = exp_fitted) %>% 
  mutate(.estimate = round(.estimate, 4))
#rcuadrado <- metricas1$.estimate[2]
#cat("R-cuadrado = ", rcuadrado)
#metricas1
# Calcular el R² ajustado manualmente
n_rob <- nrow(pred_modelo_robusto_multiple)  # Número de observaciones
p_rob <- length(coef(modelo_multiple_lmrob_2)) - 1  # Número de predictores (restamos 1 por el intercepto)

r2_rob <- metricas2 %>% filter(.metric == "rsq") %>% pull(.estimate)
r2_ajustado_rob <- 1 - ((1 - r2_rob) * (n_rob - 1) / (n_rob - p_rob - 1))

# Mostrar el R² ajustado
cat("R^2 ajustado = ", r2_ajustado_rob)
## R^2 ajustado =  0.3002414

Interpretación del Modelo

  • El modelo utiliza la transformación logarítmica de la variable precio y mantiene las mismas variables predictoras
  • Los coeficientes más significativos son:
    • Goles: +0.111 (p-value < 2e-16). Esto indica que el precio de los jugadores que tienen misma cantidad de asistencias y edad aumenta un 11.7% por cada gol anotado.
    • Asistencias: +0.170 (p-value < 2e-16). Esto indica que el precio de los jugadores que tienen misma cantidad de goles y edad aumenta un 18.5% por cada asistencia adicional.
    • Edad: efecto cuadrático significativo (p-value < 2e-16)
    • América: +0.440 respecto a África (p-value = 0.0008), indica un aumento estadísticamete significativo de 55.2% en el precio de los jugadores americanos con respecto a los africanos. Es mayor con respecto al modelo clásico, con lo cual al quitarle peso a los jugadores mas caros podemos decir que los jugadores americanos son aún mas apreciados que los africanos.
    • Premier League: +0.917 respecto a la liga de referencia, (p-value < 3.66e-16) indica un aumento estadísticamente significativo de 150.2% en el precio de los jugadores de la Premier League con respecto a los jugadores de La Liga (España)
    • Ligue 1 (Francia): -0.231 respecto a la liga de referencia, (p-value = 0.03) indica una disminución estadísticamente significativa de 20.6% en el precio de los jugadores de la Ligue 1 con respecto a los jugadores de La Liga.
    • Bundesliga: -0.282 respecto de la liga de referencia (p-value = 0.004) indica una disminución estadísticamente significativa de 24.6% en el preicio de los jugadores de la Bundesliga con respecto a La Liga.
  • El R² ajustado es 0.30, similar al modelo clásico.

Evaluación y comparación de modelos frente a datos de prueba

modelos_comparacion <- list(multiple_2 = modelo_clasico_multiple,
                robusto_2 = modelo_multiple_lmrob_2)

lista_predicciones_testing = map(.x = modelos_comparacion, .f = augment, newdata = test_data) 


metricas2_test = lista_predicciones_testing$multiple_2 %>%  
  mutate(exp_fitted= exp(.fitted)) %>% 
  metrics(truth=precio, estimate=exp_fitted) %>%
  mutate(.estimate=round(.estimate, 4))

metricas3_test = lista_predicciones_testing$robusto_2 %>% 
  mutate(exp_fitted= exp(.fitted)) %>%
  metrics(truth=precio, estimate=exp_fitted) %>%
  mutate(.estimate=round(.estimate, 4))


metrica <- rbind(metricas2_test[c(1,3),], metricas3_test[c(1,3),])

modelitos_comparacion <- c(rep("Multiple - log(Precio)",2),
               rep("Robusto - log(Precio) - psi = bisquare",2))
metricas <- cbind(modelitos_comparacion, metrica)
kable(metricas)
modelitos_comparacion .metric .estimator .estimate
Multiple - log(Precio) rmse standard 17508456
Multiple - log(Precio) mae standard 7357616
Robusto - log(Precio) - psi = bisquare rmse standard 17592430
Robusto - log(Precio) - psi = bisquare mae standard 7302473
  • El RMSE es menor para el modelo clásico, sin embargo, al estar trabajando con datos que tienen outliers lo correcto es compararlo contra los valores de MAE.
  • El MAE es menor para el modelo robusto, mostrando un mejor desempeño frente al modelo clásico.

Modelos Robustos con otras funciones \(\rho\)

Se aplica la misma fórmula que el modelo anterior pero en lugar de usar la función \(\psi\) por defecto bisqueare hacemos 5 modelos nuevos usando en cada uno diferentes \(\rho\): lqq, welsh, optimal, hampel y ggw.

Evaluación de todos los modelos

modelo_multiple_lmrob_lqq <- lmrob(formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + 
                                   continente + current_club_domestic_competition_id, 
                                 data=train_data,
                                 psi = "lqq")

modelo_multiple_lmrob_lqq_2 <- lmrob(formula = precio ~ Gls + Age + I(Age^2) + Ast + 
                                   continente + current_club_domestic_competition_id, 
                                 data=train_data,
                                 psi = "lqq")

modelo_multiple_lmrob_welsh <- lmrob(formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + 
                                     continente + current_club_domestic_competition_id, 
                                   data=train_data,
                                   psi = "welsh")

modelo_multiple_lmrob_welsh_2 <- lmrob(formula = precio ~ Gls + Age + I(Age^2) + Ast + 
                                     continente + current_club_domestic_competition_id, 
                                   data=train_data,
                                   psi = "welsh")

modelo_multiple_lmrob_optimal <- lmrob(formula = log(precio) ~ Gls + Age + I(Age^2) + Ast + 
                                       continente + current_club_domestic_competition_id, 
                                     data=train_data,
                                     psi = "optimal")

modelo_multiple_lmrob_optimal_2 <- lmrob(formula = precio ~ Gls + Age + I(Age^2) + Ast + 
                                       continente + current_club_domestic_competition_id, 
                                     data=train_data,
                                     psi = "optimal")

# Agrupar todos los modelos
modelos <- list(
    multiple_1 = modelo_clasico_multiple_1, 
    multiple_2 = modelo_clasico_multiple,
    robusto_1 = modelo_multiple_lmrob,
    robusto_2 = modelo_multiple_lmrob_2,
    robusto_3_1 = modelo_multiple_lmrob_lqq_2,
    robusto_3 = modelo_multiple_lmrob_lqq,
    robusto_4_1 = modelo_multiple_lmrob_welsh_2,
    robusto_4 = modelo_multiple_lmrob_welsh,
    robusto_5_1 = modelo_multiple_lmrob_optimal_2,
    robusto_5 = modelo_multiple_lmrob_optimal
)

# Definir nombres de modelos
modelitos <- c(rep("Clasico - Precio",3),
               rep("Clasico - log(Precio)",3),
               rep("Robusto - Precio",3),
               rep("Robusto - log(Precio) - psi = bisquare",3),
               rep("Robusto - Precio - psi = lqq",3),
               rep("Robusto - log(Precio) - psi = lqq",3),
               rep("Robusto - Precio - psi = welsh",3), 
               rep("Robusto - log(Precio) - psi = welsh",3), 
               rep("Robusto - Precio - psi = optimal",3),
               rep("Robusto - log(Precio) - psi = optimal",3))

# Calcular métricas para entrenamiento y prueba
calcular_metricas <- function(modelo, datos, tipo) {
  pred <- augment(modelo, newdata = datos)
  if("log" %in% names(modelo$call)) {
    pred$pred_final <- exp(pred$.fitted)
  } else {
    pred$pred_final <- pred$.fitted
  }
  
  metrics(data = pred, truth = precio, estimate = pred_final) %>%
    mutate(.estimate = round(.estimate, 4),
           tipo_datos = tipo)
}

# Calcular métricas
metricas_entrenamiento <- map_dfr(modelos, ~calcular_metricas(., train_data, "Entrenamiento"), .id = "modelo")
metricas_prueba <- map_dfr(modelos, ~calcular_metricas(., test_data, "Prueba"), .id = "modelo")

# Crear dataframe de métricas
df_metricas <- data.frame(
  modelo = rep(modelitos, 2),
  tipo_datos = c(metricas_entrenamiento$tipo_datos, metricas_prueba$tipo_datos),
  metrica = c(metricas_entrenamiento$.metric, metricas_prueba$.metric),
  valor = c(metricas_entrenamiento$.estimate, metricas_prueba$.estimate)
) %>%
  distinct() %>%
  filter(metrica %in% c("rmse", "mae", "rsq"))

# Gráfico RMSE
ggplot(df_metricas %>% filter(metrica == "rmse"), 
       aes(x = valor, y = modelo, fill = tipo_datos)) +
  geom_col(position = "dodge") +
  geom_text(aes(label = scales::comma(valor)), 
            position = position_dodge(width = 0.9),
            hjust = 1,
            color = "black",
            size = 3) +
  labs(title = "Comparación de RMSE por Modelo",
       x = "RMSE",
       y = "Modelo",
       fill = "Conjunto de datos") +
  scale_fill_manual(values = c("Entrenamiento" = "#99CCFF", "Prueba" = "#003366"))

# Gráfico MAE
ggplot(df_metricas %>% filter(metrica == "mae"), 
       aes(x = valor, y = modelo, fill = tipo_datos)) +
  geom_col(position = "dodge") +
  geom_text(aes(label = scales::comma(valor)), 
            position = position_dodge(width = 0.9),
            hjust = 1,
            color = "black",
            size = 3) +
  labs(title = "Comparación de MAE por Modelo",
       x = "MAE",
       y = "Modelo",
       fill = "Conjunto de datos") +
  scale_fill_manual(values = c("Entrenamiento" = "#99FF99", "Prueba" = "#006600"))

# Gráfico R²
ggplot(df_metricas %>% filter(metrica == "rsq"), 
       aes(x = valor, y = modelo, fill = tipo_datos)) +
  geom_col(position = "dodge") +
  geom_text(aes(label = scales::percent(valor)), 
            position = position_dodge(width = 0.9),
            hjust = 1,
            color = "black",
            size = 3) +
  labs(title = "Comparación de R² por Modelo",
       x = "R² (porcentaje)",
       y = "Modelo",
       fill = "Conjunto de datos") +
  scale_fill_manual(values = c("Entrenamiento" = "#FFB6C1", "Prueba" = "#8B0000"))

  • El MAE de todos los modelos robustos es menor que el de los modelos clásicos
  • El modelo que utiliza la función \(\rho\) optimal tiene le menor MAE, siendo el de mejor desempeño. Esto es posible ya ya que la funcion optimal es apropiada para datos donde los outliers no son muy pronunciados, que parece ser el caso del precio de los jugadores.

Conclusiones

El análisis estadístico revela patrones fundamentales en la valoración de jugadores que desafían varias percepciones tradicionales del mercado.

La evidencia empírica demuestra una clara segmentación del mercado europeo, con ineficiencias significativas que crean oportunidades estratégicas.

La elección metodológica de utilizar regresión robusta con transformación logarítmica demostró ser crucial para la validez del análisis.

El método MM-estimation implementado en Robustbase, que utiliza por defecto la función bisquare, permitió manejar eficazmente la heterogeneidad inherente al mercado de fichajes, donde las valoraciones extremas son comunes pero no necesariamente outliers estadísticos.

La transformación logarítmica no solo mejoró las propiedades estadísticas del modelo, sino que también proporcionó una interpretación más intuitiva en términos de variaciones porcentuales, alineándose naturalmente con la forma en que el mercado evalúa los cambios en el valor de los jugadores.

El uso de estimadores robustos reveló que el mercado de fichajes, aunque volátil, mantiene una estructura subyacente que puede ser modelada de manera más precisa cuando se utilizan métodos que equilibran robustez y eficiencia estadística.

El modelo robusto, con un R² ajustado de 30%, captura las dinámicas fundamentales del mercado mientras filtra el “ruido” especulativo.

La efectividad de la función bisquare sugiere que, contrario a la percepción popular, las valoraciones extremas en el mercado siguen patrones identificables y no son puramente especulativas.

La Premier League mantiene una prima de valoración del 150.2% sobre La Liga, un diferencial que excede significativamente las diferencias en ingresos operativos entre estas ligas.

Esta sobrevaloración sistemática sugiere una burbuja estructural en el mercado inglés, particularmente notable en comparación con Bundesliga (-24.6%) y Ligue 1 (-20.6%), donde el talento parece estar sistemáticamente infravalorado.

Una observación particularmente relevante es la mayor valoración de las asistencias (+18.5%) sobre los goles (+11.7%). Esta diferencia refleja una evolución en la comprensión del valor creativo en el fútbol moderno, donde la capacidad de generar oportunidades se premia por encima de la finalización. Este patrón sugiere una sofisticación creciente en la evaluación del talento.

La relación cuadrática con la edad emerge como un factor crítico en la valoración, señalando una ventana óptima de valoración que el mercado reconoce consistentemente. Este patrón tiene implicaciones profundas para la gestión de activos deportivos y la planificación de plantillas a largo plazo.

La brecha de valoración del 55.2% entre jugadores americanos y africanos, controlando por rendimiento y liga, revela un sesgo de mercado significativo. Esta disparidad, estadísticamente significativa (p-value = 0.0008), indica una ineficiencia de mercado estructural que trasciende el rendimiento deportivo puro.

Las ineficiencias identificadas en el mercado sugieren que el valor real de un jugador puede diferir significativamente de las valoraciones de mercado actuales, especialmente en ligas secundarias y mercados emergentes. La transformación logarítmica del modelo revela que estas discrepancias siguen patrones predecibles y explotables.

El análisis también señala una evolución en la estructura del mercado, donde los factores tradicionales de valoración están siendo complementados por métricas más sofisticadas. La significativa prima por creatividad sugiere un mercado que está comenzando a valorar más acertadamente las contribuciones tácticas complejas.

Estos hallazgos indican que el mercado de transferencias, aunque cada vez más sofisticado, mantiene ineficiencias estructurales significativas. La combinación de sesgos geográficos, primas de liga y valoración de habilidades específicas crea un panorama complejo pero analíticamente navegable para la identificación de valor.

Bibliografía

LS0tCnRpdGxlOiAiUHJlZGljY2nDs24gZGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzIGVuIGxhcyA1IGxpZ2FzIGRlIGZ1dGJvbCBldXJvcGVhcyBtYXMgaW1wb3J0YW50ZXMiCmF1dGhvcjogIlJvZHJpZ28gTWFycXVlcywgQWd1c3TDrW4gQ2VwZWRhLCBHZXJtw6FuIEZlcm7DoW5kZXoiCmRhdGU6ICIxNSBkZSBEaWNpZW1icmUgZGUgMjAyNCIKc3VidGl0bGU6ICJFbmZvcXVlIEVzdGFkw61zdGljbyBkZWwgQXByZW5kaXphamUiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IHVuaXRlZAogICAgY29kZV9kb3dubG9hZDogeWVzCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KZGl2Lm1haW4tY29udGFpbmVyIHsKICBtYXgtd2lkdGg6IDE2MDBweDsKICBtYXJnaW4tbGVmdDogYXV0bzsKICBtYXJnaW4tcmlnaHQ6IGF1dG87Cn0KPC9zdHlsZT4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojTGlicmVyaWFzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoTUFTUykKbGlicmFyeShyb2J1c3RiYXNlKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmBgYAoKIyBPYmpldGl2bwoKRWwgZsO6dGJvbCBlcyBlbCBkZXBvcnRlIG3DoXMgcG9wdWxhciwgZWwgcXVlIHRpZW5lIHVuIG1lcmNhZG8gbcOhcyBncmFuZGUgeSBlbiBlbCBjdWFsIGVsIHZhbG9yIGRlIGxhcyB0cmFuc2ZlcmVuY2lhcyBkZSBsb3MganVnYWRvcmVzIGVzIGVsIG3DoXMgYWx0by4gRW4gZXNlIHNlbnRpZG8sIG5vcyBwcm9wb25lbW9zIGFuYWxpemFyIGxhcyB0cmFuc2ZlcmVuY2lhcyB5IGNvdGl6YWNpb25lcyBkZSBsb3MganVnYWRvcmVzIGRlIGxhcyBjaW5jbyBsaWdhcyBtYXMgaW1wb3J0YW50ZXMgZGUgRXVyb3BhOgoKLSBMYSBMaWdhIC0gRXNwYcOxYQotIFByZW1pZXIgTGVhZ3VlIC0gSW5nbGF0ZXJyYQotIEJ1bmRlc2xpZ2EgLSBBbGVtYW5pYQotIFNlcmllIEEgLSBJdGFsaWEKLSBMaWd1ZSAxIC0gRnJhbmNpYQoKUG9yIGVzdGUgbW90aXZvLCB5IGhhY2llbmRvIHVzbyBkZSBsb3MgY29ub2NpbWllbnRvcyBhcG9ydGFkb3MgcG9yIGxhIG1hdGVyaWEsIG9wdGFtb3MgcG9yIHByb2Z1bmRpemFyIGVuIGVsIGRlc2Fycm9sbG8gZGUgbcOpdG9kb3MgZGUgcmVncmVzacOzbiByb2J1c3RvcyBwYXJhIGhhY2VyIGFuw6FsaXNpcyBwcmVkaWN0aXZvcywgaGFjaWVuZG8gZm9jbyBlbiBsYSBleHBsaWNhYmlsaWRhZCBkZWwgbW9kZWxvIHBhcmEgcHJlZGVjaXIgbGFzIHRyYW5zZmVyZW5jaWFzIGRlIGxvcyBqdWdhZG9yZXMuCgpFbCBvYmpldGl2byBkZWwgcHJlc2VudGUgdHJhYmFqbyBjb25zaXN0ZSBlbiBsYSB1dGlsaXphY2nDs24gZGlzdGludG9zIG3DqXRvZG9zIHJvYnVzdG9zIHBhcmEgcHJlZGVjaXIgbGEgdmFyaWFibGUgb2JqZXRpdm86IGVsIHByZWNpbyBkZSB0cmFuc2ZlcmVuY2lhIGRlIGxvcyBqdWdhZG9yZXMuCgotIE9ic2VydmFyIHNpIGVsIHBhw61zIGRlIG9yaWdlbiBlcyB1bmEgdmFyaWFibGUgZXhwbGljYXRpdmEgY29uIHJlc3BlY3RvIGEgbGEgdmFyaWFibGUgb2JqZXRpdm8uCi0gSWRlbnRpZmljYXIgc2kgbG9zIHJhdGlvcyBxdWUgZGVtdWVzdHJhbiBlbCBkZXNlbXBlw7FvIGRlIGxvcyBqdWdhZG9yZXMgZ2VuZXJhbiBpbXBhY3RvIHNvYnJlIGxhIHRyYW5zZmVyZW5jaWEgZGUgbG9zIGp1Z2Fkb3Jlcy4KLSBVdGlsaXphciBsYSByZWdyZXNpw7NuIHJlZ3Jlc2nDs24gZGUgbcOtbmltb3MgY3VhZHJhZG9zIHBvbmRlcmFkb3MsIHJlZ3Jlc2nDs24gZGUgbcOtbmltb3MgY3VhZHJhZG9zIHJvYnVzdG9zIChIdWJlcikuCi0gQ29tcGFyYXIgZWwgZGVzZW1wZcOxbyBkZSBsb3MgZGlzdGludG9zIG1vZGVsb3MgcGFyYSBwcmVkZWNpciBsYSB2YXJpYWJsZSBvYmpldGl2by4KCgojIEludHJvZHVjY2nDs24KCkVuIGVsIG3DqXRvZG8gbGluZWFsIGNsw6FzaWNvIHNlIHV0aWxpemEgZWwgbcOpdG9kbyBkZSBjdWFkcmFkb3MgbcOtbmltb3MgcGFyYSBlbmNvbnRyYXIgbG9zIHBhcsOhbWV0cm9zICRcYmV0YSQuCgpMYSBmdW5jacOzbiBkZSBww6lyZGlkYSBxdWUgc2UgcXVpZXJlIG1pbmltaXphciBlcyBsYSBzdW1hIGRlbCBjdWFkcmFkbyBkZSBsb3MgcmVzaWR1b3MuCgokJApnKGEsIGIpID0gXHN1bV97aT0xfV5uIFxsZWZ0KCBZX2kgLSBcbGVmdCggYSArIGIgWF9pIFxyaWdodCkgXHJpZ2h0KV4yICBccXVhZCBcdGV4dHsoMSl9CiQkCgpFbiBsb3MgbW9kZWxvcyBsaW5lYWxlcyByb2J1c3RvcyBxdWVyZW1vcyBjYW1iaWFyIGxhIGZ1bmNpw7NuIGRlIHBlcmRpZGEgdGFsIHF1ZToKCi0gU2VhIEluc2Vuc2libGUgYSB2YWxvcmVzIGV4dHJlbWFkYW1lbnRlIGdyYW5kZXMgbyBvdXRsaWVycyhvIHJlc2lkdW9zIGdyYW5kZXMpCi0gQ3JlemNhIG1lbm9zIHF1ZSBtw61uaW1vcyBjdWFkcmFkb3MgY3VhbmRvIG1pcmFtb3MgbG8gc3VmaWNpZW50ZW1lbnRlIGxlam9zIGRlbCBjZXJvCi0gVGVuZ2EgQWx0YSBlZmljaWVuY2lhOiBzaSBsb3MgZGF0b3MgZGUgbGEgbXVlc3RyYSBzaWd1aWVyYW4gZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gY29uIGVycm9yZXMgbm9ybWFsZXMsIHF1ZXJlbW9zIHF1ZSBlbCBlc3RpbWFkb3IgKGJldGEpIHF1ZSBlbCBtw6l0b2RvIHJvYnVzdGEgY2FsY3VsYSBzZSBwYXJlemNhIGFsIGRlIG3DrW5pbW9zIGN1YWRyYWRvcywgcG9yIGxvIHF1ZSBsYSBmdW5jacOzbiBkZSBww6lyZGlkYSBxdWUgZWwgbcOpdG9kbyByb2J1c3RvIGNhbGN1bGEgZGViZXLDrWEgcGFyZWNlcnNlIGFsIGRlIG3DrW5pbW9zIGN1YWRyYWRvLgoKCiQkCmcoYSwgYikgPSBcc3VtX3tpPTF9Xm4gXHJobyBcbGVmdCggXGZyYWN7WV9pIC0gXGxlZnQoIGEgKyBiIFhfaSBccmlnaHQpfXtzX259IFxyaWdodCkgICBccXVhZCBcdGV4dHsoMil9IAokJAoKKipEZWZpbmljacOzbiBkZSBsYSBmdW5jacOzbiDPgSoqCgpTZWEgz4E6IFIg4oaSIFIgdW5hIGZ1bmNpw7NuIHF1ZSBjdW1wbGUgbGFzIHNpZ3VpZW50ZXMgcHJvcGllZGFkZXM6CgoxLiAqKkRvbWluaW8geSBDb2RvbWluaW8qKgogICAtIM+BOiBSIOKGkiBSCgoyLiAqKlByb3BpZWRhZGVzIEZ1bmRhbWVudGFsZXMqKgogICAtIM+BKDApID0gMAogICAtIM+BKC11KSA9IM+BKHUpIFtTaW1ldHLDrWFdCiAgIC0gU2kgMCDiiaQgdSDiiaQgdiBlbnRvbmNlcyDPgSh1KSDiiaQgz4EodikgW01vbm90b27DrWFdCiAgIC0gz4EgZXMgY29udGludWEKICAgLSBzdXB7z4EodSk6IHUg4oiIIFJ9ID0gMSBbQWNvdGFjacOzbl0KCjMuICoqUHJvcGllZGFkIGRlIENyZWNpbWllbnRvIEVzdHJpY3RvKioKICAgLSBTaSDPgSh1KSA8IDEgeSAwIOKJpCB1IDwgdiBlbnRvbmNlcyDPgSh1KSA8IM+BKHYpCgpFc3RhIGZ1bmNpw7NuIM+BIGVzIGFjb3RhZGEsIGNyZWNpZW50ZSB5IHNpbcOpdHJpY2EgYWxyZWRlZG9yIGRlbCBjZXJvLCBjYXJhY3RlcsOtc3RpY2FzIHF1ZSBsYSBoYWNlbiBlc3BlY2lhbG1lbnRlIMO6dGlsIHBhcmEgbGEgZXN0aW1hY2nDs24gcm9idXN0YS4KClVuYSBwb3NpYmlsaWRhZCBlcyBhanVzdGFyIHVuYSByZWN0YSB1c2FuZG8gdW4gcHJvY2VkaW1pZW50byBkZSBhanVzdGUgcm9idXN0bywgcG9yIGVqZW1wbG8gdW4gTU0tZXN0aW1hZG9yIGRlIHJlZ3Jlc2nDs24sIHByb3B1ZXN0byBwb3IgWW9oYWkgWzE5ODddLiBFbiBSLCBlc3RvIGVzdMOhIHByb2dyYW1hZG8gZGVudHJvIGRlIGxhIHJ1dGluYSBsbXJvYiBlbiBlbCBwYXF1ZXRlIHJvYnVzdGJhc2UgZGUgUi4gTGEgZXN0aW1hY2nDs24gc2UgaGFjZSBlbiB0cmVzIGV0YXBhcywgc2UgcHJvcG9uZSB1biBlc3RpbWFkb3IgaW5pY2lhbCBkZSBsb3MgcGFyw6FtZXRyb3MsIGEgcGFydGlyIGRlIMOpbCBzZSBlc3RpbWEgYSBzbiB5IG1hbnVhbG1lbnRlIHNlIG9idGllbmVuIGxvcyBlc3RpbWFkb3JlcyBkZSBsb3MgcGFyw6FtZXRyb3MgYSBwYXJ0aXIgZGUgZWxsb3MsIG1pbmltaXphbmRvIGxhIGZ1bmNpw7NuIG9iamV0aXZvCgpFeGlzdGVuIHZhcmlhcyBmdW5jaW9uZXMgJFxyaG8kIHF1ZSBzZXLDoW4gZXZhbHVhZGFzIGVuIGVsIHByZXNlbnRlIHRyYWJham8uIFBvciBkZWZlY3RvLCByb2J1c3RiYXNlIHV0aWxpemEgbGEgYmljdWFkcmFkYSAoYmlzcXVhcmUpLCBwZXJvIHRhbWJpw6luIHNlIHB1ZWRlbiBpbXBsZW1lbnRhciBscXEsIHdlbHNoLCBvcHRpbWFsLCBldGMuCgpSb2J1c3RiYXNlIHV0aWxpemEgZWwgYWxnb3JpdG1vIEl0ZXJhdGl2ZWx5IFJld2VpZ2h0ZWQgTGVhc3QgU3F1YXJlcyAoSVJXTFMpIHBhcmEgZXN0aW1hciBsb3MgcGFyw6FtZXRyb3MgJFxiZXRhJC4gRWwgcHJvY2VzbyBjb25zaXN0ZSBlbiBsYSBzaWd1aWVudGVzIGV0YXBhczoKCjEuIEluaWNpYWxpemFyIGVsIHByb2Nlc28gdXRpbGl6YW5kbyB1biBlc3RpbWFjacOzbiBpbmljaWFsIGRlIGEgeSBiIHV0aWxpemFuZG8gZWwgbcOpdG9kbyBkZSBjdWFkcmFkb3MgbcOtbmltb3MuCgoyLiBEZXJpdmFyIGxhIGZ1bmNpw7NuICgyKSByZXNwZWN0byBhIGxvcyBwYXLDoW1ldHJvcyBhIHkgYjoKJCQKXGZyYWN7XHBhcnRpYWwgZ317XHBhcnRpYWwgYX0gPSBcc3VtX3tpPTF9XntufSBccHNpIFxsZWZ0KFxmcmFje1lfaSAtIChhICsgYlhfaSl9e3Nfbn0gXHJpZ2h0KSBcY2RvdCBcZnJhY3stMX17c19ufSBccXVhZCBcdGV4dHsoMyl9CiQkCiQkClxmcmFje1xwYXJ0aWFsIGd9e1xwYXJ0aWFsIGJ9ID0gXHN1bV97aT0xfV57bn0gXHBzaSBcbGVmdChcZnJhY3tZX2kgLSAoYSArIGJYX2kpfXtzX259IFxyaWdodCkgXGNkb3QgXGZyYWN7LVhfaX17c19ufSBccXVhZCBcdGV4dHsoNCl9CiQkCgpkb25kZSAKCiQkClxmcmFje2QgXHJobyh4KX17ZHh9ID0gXHBzaSh4KSBccXVhZCBcdGV4dHsoNSl9CiQkCgozLiBTZSBjYWxjdWxhbiBsb3MgcGVzb3MgZGUgY2FkYSBvYnNlcnZhY2lvbgoKJCQKd19pID0gXGZyYWN7XHBzaSBcbGVmdChcZnJhY3tZX2kgLSAoYSArIGJYX2kpfXtzfSBccmlnaHQpfXtcZnJhY3tZX2kgLSAoYSArIGJYX2kpfXtzfX0gXHF1YWQgXHRleHR7KDYpfQokJAoKNC4gU2UgbXVsdGlwbGljYSBhIGNhZGEgb2JzZXJ2YWNpw7NuIHBvciBzdSByZXNwZWN0aXZvIHBlc28uCgo1LiBTZSB2dWVsdmVuIGEgZXN0aW1hciBsb3MgcGFyw6FtZXRyb3MgYSB5IGIgY29uIHVuYSByZWdyZXNpw7NuIHBvbmRlcmFkYS4KClNlIHJlcGl0ZW4gbG9zIHBhc29zIGhhc3RhIG5vIG9ic2VydmFyIG1hcyBtZWpvcmFzIG8gaGFzdGEgdW4gbcOheGltbyBkZSBpdGVyYWNpb25lcy4KCkEgY29udGludWFjacOzbiBzZSBtdWVzdHJhbiBsYXMgZnVuY2lvbmVzICRccmhvJCBtw6FzIGNvbXVuZXMgeSBjb21vIHNlIGNvbXBvcnRhbiBsb3MgcGVzb3MgZGUgbGFzIG1pc21hcy4KCjEuIEJpc3F1YXJlIChUdWtleSk6CiQkClxyaG8oeCkgPSBcYmVnaW57Y2FzZXN9CjEgLSAoMSAtICh4L2spXjIpXjMgJiBcdGV4dHtzaSB9IHx4fCBcbGVxIGsgXFwKMSAmIFx0ZXh0e3NpIH0gfHh8ID4gawpcZW5ke2Nhc2VzfSBccXVhZCBcdGV4dHsoNyl9CiQkCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIHNldCBtYXJnaW5zIGZvciBwbG90cwpvcHRpb25zKFN3ZWF2ZUhvb2tzPWxpc3QoZmlnPWZ1bmN0aW9uKCkgcGFyKG1hcj1jKDMsMywxLjQsMC43KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZ3A9YygxLjUsIDAuNSwgMCkpKSkKIyMgeCBheGlzIGZvciBwbG90czoKeC4gPC0gc2VxKC01LCAxMCwgbGVuZ3RoLm91dCA9IDE1MDEpCnNvdXJjZShzeXN0ZW0uZmlsZSgieHRyYVIvcGxvdC1wc2lGdW4uUiIsIHBhY2thZ2UgPSAicm9idXN0YmFzZSIsIG11c3RXb3JrPVRSVUUpKQpnZXRPcHRpb24oIlN3ZWF2ZUhvb2tzIilbWyJmaWciXV0oKQpwLnBzaUZ1bih4LiwgImJpd2VpZ2h0IiwgcGFyID0gNC42ODUpCmBgYAoKMi4gSHViZXI6CiQkClxyaG8oeCkgPSBcYmVnaW57Y2FzZXN9ClxmcmFje3heMn17Mn0gJiBcdGV4dHtzaSB9IHx4fCBcbGVxIGsgXFwKa3x4fCAtIFxmcmFje2teMn17Mn0gJiBcdGV4dHtzaSB9IHx4fCA+IGsKXGVuZHtjYXNlc30gXHF1YWQgXHRleHR7KDgpfQokJAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmdldE9wdGlvbigiU3dlYXZlSG9va3MiKVtbImZpZyJdXSgpCnBsb3QoaHViZXJQc2ksIHguLCB5bGltPWMoLTEuNCwgNSksIGxlZy5sb2M9InRvcHJpZ2h0IiwgbWFpbj1GQUxTRSkKYGBgCgozLiBXZWxzaDoKJCQKXHJobyh4KSA9IDEgLSBcZXhwKC14XjIva14yKSBccXVhZCBcdGV4dHsoOSl9CiQkCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2V0T3B0aW9uKCJTd2VhdmVIb29rcyIpW1siZmlnIl1dKCkKcC5wc2lGdW4oeC4sICJXZWxzaCIsIHBhciA9IDIuMTEpCmBgYAoKNC4gSGFtcGVsOgokJApccmhvKHgpID0gXGJlZ2lue2Nhc2VzfQpcZnJhY3t4XjJ9ezJ9ICYgXHRleHR7c2kgfSB8eHwgXGxlcSBhIFxcCmF8eHwgLSBcZnJhY3thXjJ9ezJ9ICYgXHRleHR7c2kgfSBhIDwgfHh8IFxsZXEgYiBcXApcZnJhY3thKGN8eHwgLSB4XjIvMiAtIGJjICsgYl4yLzIpfXtjLWJ9ICYgXHRleHR7c2kgfSBiIDwgfHh8IFxsZXEgYyBcXAphKGMgLSBcZnJhY3tifXsyfSkgJiBcdGV4dHtzaSB9IHx4fCA+IGMKXGVuZHtjYXNlc30gXHF1YWQgXHRleHR7KDEwKX0KJCQKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZXRPcHRpb24oIlN3ZWF2ZUhvb2tzIilbWyJmaWciXV0oKQojIyBzZWUgYWxzbyBoYW1wZWxQc2kKcC5wc2lGdW4oeC4sICJIYW1wZWwiLCBwYXIgPSAjIyBEZWZhdWx0LCBidXQgcm91bmRlZDoKICAgICAgICAgICByb3VuZChjKDEuNSwgMy41LCA4KSAqIDAuOTAxNjA4NSwgMSkpCmBgYAoKNS4gTFFRIChMaW5lYXJseSBRdWFkcmF0aWNhbGx5IFF1YWRyYXRpYyk6CiQkClxyaG8oeCkgPSBcYmVnaW57Y2FzZXN9ClxmcmFje3heMn17Mn0gJiBcdGV4dHtzaSB9IHx4fCBcbGVxIGNfMSBcXApjXzF8eHwgLSBcZnJhY3tjXzFeMn17Mn0gJiBcdGV4dHtzaSB9IGNfMSA8IHx4fCBcbGVxIGNfMiBcXApcZnJhY3soY18zfHh8IC0geF4yLzIpfXtjXzMtY18yfSAmIFx0ZXh0e3NpIH0gY18yIDwgfHh8IFxsZXEgY18zIFxcCmNfMyAmIFx0ZXh0e3NpIH0gfHh8ID4gY18zClxlbmR7Y2FzZXN9IFxxdWFkIFx0ZXh0eygxMSl9CiQkCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2V0T3B0aW9uKCJTd2VhdmVIb29rcyIpW1siZmlnIl1dKCkKcC5wc2lGdW4oeC4sICJMUVEiLCBwYXIgPSBjKC0uNSwxLjUsLjk1LE5BKSkKYGBgCgo2LiBPcHRpbWFsCiQkClxwc2koeCkgPSBcYmVnaW57Y2FzZXN9IAp4ICYgXHRleHR7c2kgfSB8eHwgXGxlcSBhIFxcCmEgXGNkb3QgXHRleHR7c2lnbn0oeCkgXGNkb3QgXGxlZnQoMSAtIFxsZWZ0KFxmcmFje3x4fCAtIGF9e2IgLSBhfVxyaWdodCleMlxyaWdodCleMiAmIFx0ZXh0e3NpIH0gYiA8IHx4fCBcbGVxIGMgXFwKMCAmIFx0ZXh0e3NpIH0gfHh8ID4gYyAKXGVuZHtjYXNlc30gXHF1YWQgXHRleHR7KDEyKX0KJCQKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZXRPcHRpb24oIlN3ZWF2ZUhvb2tzIilbWyJmaWciXV0oKQpwLnBzaUZ1bih4LiwgIm9wdGltYWwiLCBwYXIgPSAxLjA2LCBsZWcubG9jPSJib3R0b21yaWdodCIpCmBgYAoKRG9uZGUgJGskLCAkYSQsICRiJCwgJGMkLCAkY18xJCwgJGNfMiQgeSAkY18zJCBzb24gY29uc3RhbnRlcyBxdWUgZGV0ZXJtaW5hbiBsb3MgcHVudG9zIGRlIHF1aWVicmUgeSBsYSBmb3JtYSBkZSBjYWRhIGZ1bmNpw7NuLgoKRXhpc3RlbiBtdWNoYXMgb3RyYXMgcHJvcHVlc3RhcyBkZSBlc3RpbWFkb3JlcyByb2J1c3RvcyBwYXJhIHJlZ3Jlc2nDs24sIHBvciBlamVtcGxvIExNUyAobGVhc3QgbWVkaWFuIG9mIHNxdWFyZXMpLCBMVFMgKGxlYXN0IHRyaW1tZWQgc3F1YXJlcyksIM+E4oiSZXN0aW1hZG9yZXMgZGUgcmVncmVzacOzbiwgeSBjYXNpIHRvZGFzIGVzdMOhbiBpbXBsZW1lbnRhZGFzIGVuIFIuCgoKIyBEYXRhc2V0CgpFbCBzZXQgZGUgZGF0b3MgY29uc2lzdGUgZW4gZG9zIGJhc2VzLCBhbWJhcyBvYnRlbmlkYXMgZGUgS2FnZ2xlLgoKTGEgYmFzZSBkZSBkYXRvcyBjb24gbG9zIHByZWNpb3MgZGUgbG9zIGp1Z2Fkb3JlcyBzZSBvYnR1dm8gZGUgbGEgd2ViIFRyYW5zZmVybWFya3QuIEN1ZW50YSBjb24gMzI0MDUgcmVnaXN0cm9zLCB5IGRvcyBkZSBzdXMgdmFyaWFibGVzIHNvbiBsb3MgcHJlY2lvcyBhY3R1YWxlcyBkZSBsb3MganVnYWRvcmVzIHkgZWwgcHJlY2lvIG1hcyBhbHRhIGFsY2FuemFkby4gQWRlbcOhcyBjdWVudGEgY29uIGxhIGluZm9ybWFjacOzbiBkZWwgY2x1YiB5IGxpZ2EgYWN0dWFsIGRlIGNhZGEganVnYWRvci4KCkxhcyBvdHJhIGJhc2UgZGUgZGF0b3MgY3VlbnRhIGNvbiBsYXMgbcOpdHJpY2FzIGR1cmFudGUgbGEgdGVtcG9yYWRhIDIwMjMvMjAyNCBkZSBsb3MganVnYWRvcmVzIGRlIGxhcyA1IGxpZ2FzIG1hcyBpbXBvcnRhbnRlcyBkZWwgZsO6dGJvbCBldXJvcGVvLiBBbGd1bmFzIGRlIHN1cyB2YXJpYWJsZXMgc29uIEVkYWQsIGdvbGVzIGFub3RhZG9zLCBhc2lzdGVuY2lhcywgcG9zaWNpw7NuIGRlbnRybyBkZWwgY2FtcG8gZGUganVlZ28sIG5hY2lvbmFsaWRhZCwgZW50cmUgb3RyYXMuCgpBbWJhcyBiYXNlcyBkZSBkYXRvcyBzZSB1bmllcm9uIHBvciBlbCBub21icmUgeSBhcGVsbGlkbyBkZWwganVnYWRvciwgeSBlbCBjbHViIGFjdHVhbC4gQWRpY2lvbmFsbWVudGUsIHNlIGFncmVnw7MgbGEgdmFyaWFibGUgQ29udGluZW50ZSBhbCBkYXRhc2V0IGZpbmFsLCBsYSBjdWFsIGluZm9ybWEgZWwgY29udGluZW50ZSBuYXRhbCBkZSBjYWRhIGp1Z2Fkb3IuCgpFbCBkYXRhc2V0IHNlIGRpdmlkZSBlbiBFbnRyZW5hbWllbnRvIHkgUHJ1ZWJhLCBkZSBtYW5lcmEgZXN0cmF0aWZpY2FkYSBzZWfDum4gZWwgcHJlY2lvLiBTZSB1dGlsaXphIGVsIHNpZ3VpZW50ZSBib3hwbG90IHBhcmEgZ2VuZXJhciB1bmEgbnVldmEgY2F0ZWdvcsOtYSBxdWUgY2F0ZWdvcmljZSBzZWfDum4gZWwgcHJlY2lvIGRlbCBqdWdhZG9yLgoKTHVlZ28gdXRpbGl6YXJlbW9zIGVzdGEgbnVldmEgdmFyaWFibGUgcGFyYSByZWFsaXphciBsYSBkaXZpc2nDs24gZGVsIGRhdGFzZXQgZGUgbWFuZXJhIGVzdHJhdGlmaWNhZGEuCgojIyBDYXRlZ29yw61hcyBkZSBsYSBudWV2YSB2YXJpYWJsZQoKLSBNdXkgYmFqbzogTWVub3IgYSBtZWRpYW5hIChRMikKLSBCYWpvOiBlbnRyZSBtZWRpYW5hIChRMikgeSBjdWFydGlsIHN1cGVyaW9yIChRMykKLSBNZWRpbzogZW50cmUgY3VhcnRpbCBzdXBlcmlvciAoUTMpIHkgbMOtbWl0ZSBzdXBlcmlvciAoUTMgKyAxLjUgeCBJUVIpCi0gQWx0bzogZW50cmUgbGltaXRlIHN1cGVyaW9yIHkg4oKsMTAwLjAwMC4wMDAKLSBNdXkgQWx0bzogbWF5b3IgYSDigqwxMDAuMDAwLjAwMCAocmVwcmVzZW50YSBsb3MgMTAganVnYWRvcmVzIG1hcyBjYXJvcykKCkVsIG9iamV0aXZvIGVzIHF1ZSBxdWVkYW4gZXN0cmF0aWZpY2Fkb3MgbG9zIGp1Z2Fkb3JlcyBtYXMgY2Fyb3MgdSBvdXRsaWVycywgcG9yIGVzbyBubyBzZSByZWFsaXphIHVuYSBkaXZpc2nDs24gcG9yIGRlYmFqbyBkZSBRMi4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQ2FyZ2EgZGUgZGF0c2V0CiMgc2V0d2QoIi9Vc2Vycy9qb3JnZWZlcm5hbmRlei9Eb2N1bWVudHMvQ2llbmNpYWRlZGF0b3MvRUVBMjAyNC9UUDAyIikKc2V0d2QoIi9Vc2Vycy9ybWFycXVlcy9VQkEvRUVBL2dlcm1hbi1lZWEtMjAyNC9UUDAyIikKZGYgPC0gYXMuZGF0YS5mcmFtZShyZWFkLmNzdigiZGF0YXNldC5jc3YiKSkKZGYgPC0gYXMuZGF0YS5mcmFtZShkZikKZGYgPC0gbmEub21pdChkZikKY29sbmFtZXMoZGYpW2NvbG5hbWVzKGRmKSA9PSAibWFya2V0X3ZhbHVlX2luX2V1ciJdIDwtICJwcmVjaW8iCgojIEFncmVnYXIgbnVldmEgdmFyaWFibGUgcXVlIHRlbmdhIGVuIGN1ZW50YSBsb3MgcHJlY2lvcyBkZSBsb3MganVnYWRvcmVzIHkgcG9kZXIgaGFjZXIgdW4gc3BsaXQgCiMgdGVzdC10cmFpbiBlc3RyYXRpZmljYWRvCmNhamFfcHJlY2lvcyA8LSBib3hwbG90KGRmJHByZWNpbykKCmRmJHByZWNpb19jYXQgPC0gTkEKCmZvciAoaSBpbiAxOmxlbmd0aChkZiRwcmVjaW8pKSB7CiAgaWYgKGRmJHByZWNpb1tpXSA+PSAxMDAwMDAwMDApIHsgCiAgICBkZiRwcmVjaW9fY2F0W2ldIDwtICJtdXlfYWx0byJ9CiAgaWYgKGRmJHByZWNpb1tpXSA8IDEwMDAwMDAwMCAmJiBkZiRwcmVjaW9baV0gPj0gY2FqYV9wcmVjaW9zJHN0YXRzWzVdKSB7CiAgICBkZiRwcmVjaW9fY2F0W2ldIDwtICJhbHRvIn0KICBpZiAoZGYkcHJlY2lvW2ldIDwgY2FqYV9wcmVjaW9zJHN0YXRzWzVdICYmIGRmJHByZWNpb1tpXSA+PSBjYWphX3ByZWNpb3Mkc3RhdHNbNF0pIHsKICAgIGRmJHByZWNpb19jYXRbaV0gPC0gIm1lZGlvIn0KICBpZiAoZGYkcHJlY2lvW2ldIDwgY2FqYV9wcmVjaW9zJHN0YXRzWzRdICYmIGRmJHByZWNpb1tpXSA+PSBjYWphX3ByZWNpb3Mkc3RhdHNbM10pIHsKICAgIGRmJHByZWNpb19jYXRbaV0gPC0gImJham8ifQogIGlmIChkZiRwcmVjaW9baV0gPCBjYWphX3ByZWNpb3Mkc3RhdHNbM10pewogICAgZGYkcHJlY2lvX2NhdFtpXSA8LSAibXV5X2Jham8ifQp9CgojIFN1cG9uaWVuZG8gcXVlIGBkZiRjbGFzZWAgZXMgbGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EKc2V0LnNlZWQoMjg3NDk2NTgpICAjIEZpamFyIHNlbWlsbGEgcGFyYSByZXByb2R1Y2liaWxpZGFkCgojIENyZWFyIMOtbmRpY2VzIGVzdHJhdGlmaWNhZG9zIGJhc2Fkb3MgZW4gbGEgdmFyaWFibGUgYGNsYXNlYAp0cmFpbl9pbmRpY2VzIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYkcHJlY2lvX2NhdCwgcCA9IDAuNywgbGlzdCA9IEZBTFNFKSAgIyA3MCUgZW50cmVuYW1pZW50bwoKIyBEaXZpZGlyIGxvcyBkYXRvcwp0cmFpbl9kYXRhIDwtIGRmW3RyYWluX2luZGljZXMsIF0KdGVzdF9kYXRhIDwtIGRmWy10cmFpbl9pbmRpY2VzLCBdCmBgYAoKIyMjIFZlcmlmaWNhciBsYSBkaXN0cmlidWNpw7NuIGRlIGNsYXNlcwoKIyMjIyBEYXRvcyBkZSBFbnRyZW5hbWllbnRvCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0YWJsZSh0cmFpbl9kYXRhJHByZWNpb19jYXQpCmBgYAojIyMjIERhdG9zIGRlIHBydWViYQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGFibGUodGVzdF9kYXRhJHByZWNpb19jYXQpCmBgYAoKIyBBbsOhbGlzaXMgRXhwbG9yYXRvcmlvIGRlIERhdG9zCgojIyBHcsOhZmljb3MgZGUgYmFycmFzIGRlIGFsZ3VuYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcwoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNvbG5hbWVzKGRmKVtjb2xuYW1lcyhkZikgPT0gIm1hcmtldF92YWx1ZV9pbl9ldXIiXSA8LSAicHJlY2lvIgpkZiRjb250aW5lbnRlIDwtIGZhY3RvcihkZiRjb250aW5lbnRlLCBsZXZlbHMgPSBjKCJldXJvcGEiLCAiYW1lcmljYSIsICJhZnJpY2EiLCAiYXNpYV9vY2VhbmlhIikpCmdyYWZpY28xIDwtIGdncGxvdChkZiwgYWVzKHg9Y29udGluZW50ZSwgZmlsbCA9IGNvbnRpbmVudGUpKSArCiAgZ2VvbV9iYXIoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlID0gVFJVRSwgb3B0aW9uID0gIkQiKSArICMgUGFsZXRhIGFjY2VzaWJsZQogIGxhYnMoeSA9ICJDYW50aWRhZCIsIAogICAgICAgeCA9ICJDb250aW5lbnRlIiwKICAgICAgIHRpdGxlID0gIkNhbnRpZGFkIGRlIGp1Z2Fkb3JlcyBwb3IgY29udGluZW50ZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xKSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpkZiRDb21wIDwtIGZhY3RvcihkZiRDb21wLCBsZXZlbHMgPSBjKCJlcyBMYSBMaWdhIiwgIml0IFNlcmllIEEiLCAiZnIgTGlndWUgMSIsICJkZSBCdW5kZXNsaWdhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZW5nIFByZW1pZXIgTGVhZ3VlIikpCmdyYWZpY28yIDwtIGdncGxvdChkZiwgYWVzKHg9Q29tcCwgZmlsbCA9IENvbXApKSArIAogIGdlb21fYmFyKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUsIG9wdGlvbiA9ICJEIikgKyAjIFBhbGV0YSBhY2Nlc2libGUKICBsYWJzKHkgPSAiQ2FudGlkYWQiLCB4ID0gIkxpZ2EiLCB0aXRsZSA9ICJDYW50aWRhZCBkZSBqdWdhZG9yZXMgcG9yIGxpZ2EiKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnBvc2ljaW9uZXMgPC0gdGFibGUoZGYkcG9zaXRpb24pCnBvc2ljaW9uZXMgPC0gbmFtZXMoc29ydChwb3NpY2lvbmVzKSkKZGYkcG9zaXRpb24gPC0gZmFjdG9yKGRmJHBvc2l0aW9uLCBsZXZlbHMgPSBwb3NpY2lvbmVzKQpncmFmaWNvMyA8LSBnZ3Bsb3QoZGYsIGFlcyh4PXBvc2l0aW9uLCBmaWxsID0gcG9zaXRpb24pKSArIAogIGdlb21fYmFyKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUsIG9wdGlvbiA9ICJEIikgKyAjIFBhbGV0YSBhY2Nlc2libGUKICBsYWJzKHkgPSAiQ291bnQiLCB4ID0gIlBvc2ljaW9uZXMiLCB0aXRsZSA9ICJDYW50aWRhZCBkZSBqdWdhZG9yZXMgcG9yIHBvc2ljaW9uIikgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xLCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKZ3JhZmljbzQgPC0gZ2dwbG90KGRmLCBhZXMoeD1mb290LCBmaWxsID0gZm9vdCkpICsgCiAgZ2VvbV9iYXIoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlID0gVFJVRSwgb3B0aW9uID0gIkQiKSArICMgUGFsZXRhIGFjY2VzaWJsZQogIGxhYnMoeSA9ICJDb3VudCIsIHggPSAiUGllIGjDoWJpbCIsIHRpdGxlID0gIkNhbnRpZGFkIGRlIGp1Z2Fkb3JlcyBwb3IgcGllIGhhYmlsIikgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xLCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKZ3JpZC5hcnJhbmdlKGdyYWZpY28xLCBncmFmaWNvMiwgZ3JhZmljbzMsIGdyYWZpY280LG5yb3cgPSAyKQpgYGAKCiMjIyBKdWdhZG9yZXMgcG9yIGNvbnRpbmVudGUKCkNvbW8gZXJhIGRlIGVzcGVyYXJzZSwgcHJlZG9taW5hbiBsb3MganVnYWRvcmVzIGV1cm9wZW9zLiBMb3MgYW1lcmljYW5vcyB5IGFmcmljYW5vcyBlc3TDoW4gY2FzaSBlbiBpZ3VhbCBtZWRpZGEsIHNpZW5kbyB1biBwb2NvIG1heW9yIGxvcyBhbWVyaWNhbm9zLiBBc2lhIHkgT2NlYW7DrWEgZW4gY29uanVudG8gYXBvcnRhbiBzb2xvIHVuYSBtw61uaW1hIGNhbnRpZGFkLgoKIyMjIENhbnRpZGFkIGRlIGp1Z2Fkb3JlcyBkZSBsaWdhCgpUb2RhcyBsYXMgbGlnYXMgZXN0w6FuIGlndWFsIGRlIHJlcHJlc2VudGFkYXMgZW4gZWwgZGF0YXNldCwgY29uIGxvIGN1YWwgZWwgYW7DoWxpc2lzIGRlbCBwcmVjaW8gZGUganVnYWRvcmVzIHNlZ8O6biBsYSBsaWdhIHNlcsOhIGRlIGludGVyw6lzLgoKIyMjIEp1Z2Fkb3JlcyBwb3IgcG9zaWNpw7NuCgpMYSBtYXlvciBjYW50aWRhZCBkZSBqdWdhZG9yZXMgc29uIERlZmVuc29yZXMsIHkgZW4gbWVub3IgbWVkaWRhIGxvcyBNZWRpb2NhbXBpc3RhcyB5IERlbGFudGVyb3MsIHBlcm8gdG9kYXMgZXN0YXMgcG9zaWNpb25lcyBlc3TDoW4gdW5pZm9ybWVtZW50ZSBkaXN0cmlidWlkYXMuIENvbW8gZXMgZGUgZXNwZXJhcnNlLCBsb3MgQXJxdWVybyBlcyBsYSBwb3NpY2nDs24gZGUgbWVub3IgcmVwcmVzZW50YWNpw7NuLgoKIyMjIEp1Z2Fkb3JlcyBwb3IgcGllIGjDoWJpbC4KCkNvbW8gZXJhIGRlIGVzcGVyYXJzZSwgbWFzIGRlIGxhIG1pdGFkIGRlIGxvcyBqdWdhZG9yZXMgc29uIGRlcmVjaG9zLiBTaW4gZW1iYXJnbywgY29tcGFyYWRvIGNvbiBsYSBwcm9wb3JjacOzbiBkZSBwZXJzb25hcyBkaWVzdHJhcyBlbiBlbCBtdW5kbyAoODUlKSwgbGEgY2FudGlkYWQgZGUganVnYWRvcmVzIHp1cmRvcyBlcyBiYXN0YW50ZSBtYXlvciBhIGVzdGFyIHByb3BvcmNpw7NuLgoKIyMgQ29ycmVsb2dyYW1hCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGYgJT4lIAogIGRwbHlyOjpzZWxlY3QoQWdlLCBHbHMsIEFzdCwgcHJlY2lvKSAlPiUgCiAgbXV0YXRlKGxpZ2EgPSBkZiRjdXJyZW50X2NsdWJfZG9tZXN0aWNfY29tcGV0aXRpb25faWQpICU+JQogIGdncGFpcnMoLiwgbWFwcGluZyA9IGFlcyhjb2xvdXIgPSBsaWdhKSwKICAgICAgICAgIHVwcGVyID0gbGlzdChjb250aW51b3VzID0gd3JhcCgiY29yIiwgc2l6ZSA9IDMsIGhqdXN0PTAuNSkpLCBwcm9ncmVzcz1GQUxTRSkgKyAKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uID0gIkQiKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJEIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArIAogIHRoZW1lX2J3KCkgKwogIGxhYnModGl0bGU9J0NvcnJlbG9ncmFtYSB2YXJpYWJsZXMgY29udGludWFzJykKCmBgYAoKIyMjIENvcnJlbGFjacOzbiBlbnRyZSBHb2xlcyB5IFByZWNpbwpTZSBvYnNlcnZhIHVuYSBjb3JyZWxhY2nDs24gcG9zaXRpdmEgYWx0YSBhIG1vZGVyYWRhIChDb3JyOiAwLjUwMikuIEVzdG8gc3VnaWVyZSBxdWUgbG9zIGp1Z2Fkb3JlcyBxdWUgbWFyY2FuIG3DoXMgZ29sZXMgdGllbmRlbiBhIHRlbmVyIHVuIHZhbG9yIGRlIG1lcmNhZG8gbcOhcyBhbHRvLiBMYSByZWxhY2nDs24gcGFyZWNlIHNlciBjb25zaXN0ZW50ZSBhIHRyYXbDqXMgZGUgbGFzIGRpZmVyZW50ZXMgbGlnYXMuIFNpZW5kbyBtZW5vcyBwcm9udW5jaWFkYSBlbiBsYXMgbGlnYXMgZXNwYcOxb2xhIHkgZnJhbmNlc2EgZG9uZGUgcGFyZWNlcsOtYSBxdWUgbGEgY2FudGlkYSBkZSBnb2xlcyBhbm90YWRvcyB0aWVuZSBtZW5vcyBpbXBvcnRhbmNpYSBxdWUgZW4gZWwgcmVzdG8gZGUgbGFzIGxpZ2FzLCB5IG1hcyBwcm9udW5jaWFkYSBlbiBsYSBsaWdhIGluZ2xlc2EgZG9uZGUgcGFyZWNlcsOtYSBxdWUgbGEgY2FudGlkYSBkZSBnb2xlcyBhbm90YWRvcyB0aWVuZSBtYXMgaW1wb3J0YW5jaWEgcXVlIGVsIHJlc3RvIGRlIGxhcyBsaWdhcy4KCiMjIyBDb3JyZWxhY2nDs24gZW50cmUgQXNpc3RlbmNpYXMgeSBQcmVjaW8gIApFeGlzdGUgdW5hIGNvcnJlbGFjacOzbiBwb3NpdGl2YSBtb2RlcmFkYSAoQ29ycjogMC40NjUpLiBJbmRpY2EgcXVlIGxvcyBqdWdhZG9yZXMgcXVlIGRhbiBtw6FzIGFzaXN0ZW5jaWFzIHRhbWJpw6luIHRpZW5kZW4gYSB0ZW5lciB1biBtYXlvciB2YWxvciBlbiBlbCBtZXJjYWRvLiBMYSBjb3JyZWxhY2nDs24gZXMgYWxnbyBtZW5vciBxdWUgY29uIGxvcyBnb2xlcywgc3VnaXJpZW5kbyBxdWUgZWwgbWVyY2FkbyB2YWxvcmEgbcOhcyBsYSBjYXBhY2lkYWQgZ29sZWFkb3JhLiBTZSBvYnNlcnZhIGxhIG1pc21hIHRlbmRlbmNpYSBxdWUgcGFyYSBsYSBjYW50aWRhZCBkZSBnb2xlcywgZXMgbWVub3MgcHJvbnVuY2lhZGEgZW4gbGFzIGxpZ2FzIGVzcGHDsW9sYSB5IGZyYW5jZXNhIGRvbmRlIHBhcmVjZXLDrWEgcXVlIGxhIGNhbnRpZGEgZGUgYXNpc3RlbmNpYXMgdGllbmUgbWVub3MgaW1wb3J0YW5jaWEgcXVlIGVuIGVsIHJlc3RvIGRlIGxhcyBsaWdhcywgeSBtYXMgcHJvbnVuY2lhZGEgZW4gbGEgbGlnYSBpbmdsZXNhIGRvbmRlIHBhcmVjZXLDrWEgcXVlIGxhIGNhbnRpZGEgZGUgYXNpc3RlbmNpYXMgdGllbmUgbWFzIGltcG9ydGFuY2lhIHF1ZSBlbCByZXN0byBkZSBsYXMgbGlnYXMuCgojIyMgQ29ycmVsYWNpw7NuIGVudHJlIEVkYWQgeSBQcmVjaW8KU2Ugb2JzZXJ2YSB1bmEgY29ycmVsYWNpw7NuIG5lZ2F0aXZhIGJhamEgcGVybyBjbGFyYSAoQ29ycjogLTAuMTc2KS4gU3VnaWVyZSBxdWUgZWwgdmFsb3IgZGUgbWVyY2FkbyB0aWVuZGUgYSBkaXNtaW51aXIgY29uIGxhIGVkYWQgZGVsIGp1Z2Fkb3IuIEVzdG8gdGllbmUgc2VudGlkbyBkZXNkZSB1bmEgcGVyc3BlY3RpdmEgZGUgaW52ZXJzacOzbiwgeWEgcXVlIGxvcyBqdWdhZG9yZXMgbcOhcyBqw7N2ZW5lcyB0aWVuZW4gbWF5b3IgcG90ZW5jaWFsIGRlIGRlc2Fycm9sbG8geSBhw7FvcyBkZSBjYXJyZXJhIHBvciBkZWxhbnRlLiBFbiBlc3RlIGNhc28sIGxhIGxpZ2EgZXNwYcOxb2wgdGllbmUgdW5hIHBlbmRpZW50ZSBtYXMgbmVnYXRpdmEgbG8gcXVlIHBvZHLDrWEgZXZpZGVuY2lhciBxdWUgbG9zIGp1Z2Fkb3JlcyBqb3ZlbmVzIHNvbiBtYXMgdmFsb3JhZG9zIHF1ZSBlbCByZXN0byBkZSBsYXMgbGlnYXMsIGVuIGNhbWJpbyBlbiBsYSBsaWdhIGZyYW5jZXNhIHNlIG9ic2VydmEgdW5hIHBlbmRpZW50ZSBtYXMgc3VhdmUgY29uIGxvIGN1YWwgcG9kcsOtYSBpbmRpY2FyIHF1ZSBzZSB2YWxvcmEgbWFzIGxhIGV4cGVyaWVuY2lhIHF1ZSBlbCByZXN0byBkZSBsYXMgbGlnYXMuCgojIyMgRGlzdHJpYnVjacOzbiBwb3IgTGlnYXMKTG9zIGRpYWdyYW1hcyBkZSBjYWphIChib3hwbG90cykgbXVlc3RyYW4gZGlmZXJlbmNpYXMgZW4gbGEgZGlzdHJpYnVjacOzbiBkZSBwcmVjaW9zIGVudHJlIGxpZ2FzLiBMYXMgbGlnYXMgZXNwYcOxb2xhIHkgYnJpdMOhbmljYSBwcmVzZW50YW4gdmFsb3JlcyBtw6FzIGFsdG9zIGVuIGdlbmVyYWwuIEVzdG8gc2UgYWxpbmVhIGNvbiBlbCBwb2RlciBlY29uw7NtaWNvIGRlIGVzdGFzIGxpZ2FzIHkgZXNwZWPDrWZpY2FtZW50ZSBkZSBjbHViZXMgY29tbyBSZWFsIE1hZHJpZCB5IE1hbmNoZXN0ZXIgQ2l0eS4gTGEgZGlzdHJpYnVjacOzbiBkZSBwcmVjaW9zIGVzIG5vdGFibGVtZW50ZSBhc2ltw6l0cmljYSwgY29uIGFsZ3Vub3MgdmFsb3JlcyBtdXkgYWx0b3MgcXVlIHBvZHLDrWFuIGNvbnNpZGVyYXJzZSBvdXRsaWVycy4KCiMjIyBDb3JyZWxhY2lvbmVzIGNydXphZGFzCkV4aXN0ZSB1bmEgY29ycmVsYWNpw7NuIHBvc2l0aXZhIG1vZGVyYWRhIGVudHJlIGdvbGVzIHkgYXNpc3RlbmNpYXMgKENvcnI6IDAuNTg2KS4gRXN0byBzdWdpZXJlIHF1ZSBsb3MganVnYWRvcmVzIG3DoXMgZWZlY3Rpdm9zIHRpZW5kZW4gYSBkZXN0YWNhciB0YW50byBlbiBnb2xlcyBjb21vIGVuIGFzaXN0ZW5jaWFzLiBMYSBlZGFkIG11ZXN0cmEgY29ycmVsYWNpb25lcyBtdXkgZMOpYmlsZXMgY29uIGdvbGVzIHkgYXNpc3RlbmNpYXMuCgojIyBKdWdhZG9yZXMgbWFzIGNhcm9zCgpMb3MgMTAganVnYWRvcmVzIG1hcyBjYXJvcwpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbWFzX2Nhcm9zX25vbWJyZSA8LSBkZiAlPiUgCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gcHJlY2lvLCBuPTEwKSAgJT4lIAogIHB1bGwobmFtZSkKbWFzX2Nhcm9zX2VxdWlwbyA8LSBkZiAlPiUgCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gcHJlY2lvLCBuPTEwKSAgJT4lIAogIHB1bGwoU3F1YWQpCm1hc19jYXJvc19wcmVjaW8gPC0gZGYgJT4lIAogIHNsaWNlX21heChvcmRlcl9ieSA9IHByZWNpbywgbj0xMCkgICU+JSAKICBwdWxsKHByZWNpbykKbWFzX2Nhcm8gPC0gZGF0YS5mcmFtZShOb21icmUgPSBtYXNfY2Fyb3Nfbm9tYnJlLCAKICAgICAgICAgICAgICAgICAgICAgICBFcXVpcG8gPSBtYXNfY2Fyb3NfZXF1aXBvLCAKICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW8gPSBtYXNfY2Fyb3NfcHJlY2lvKQoKa2FibGUobWFzX2Nhcm8pCmBgYAoKIyMgSnVnYWRvcmVzIG1hcyBiYXJhdG9zCgpMb3MgMTAganVnYWRvcmVzIG1hcyBiYXJhdG9zCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptYXNfYmFyYXRvc19ub21icmUgPC0gZGYgJT4lIAogIHNsaWNlX21pbihvcmRlcl9ieSA9IHByZWNpbywgbj0xMCkgICU+JSAKICBwdWxsKG5hbWUpCm1hc19iYXJhdG9zX2VxdWlwbyA8LSBkZiAlPiUgCiAgc2xpY2VfbWluKG9yZGVyX2J5ID0gcHJlY2lvLCBuPTEwKSAgJT4lIAogIHB1bGwoU3F1YWQpCm1hc19iYXJhdG9zX3ByZWNpbyA8LSBkZiAlPiUgCiAgc2xpY2VfbWluKG9yZGVyX2J5ID0gcHJlY2lvLCBuPTEwKSAgJT4lIAogIHB1bGwocHJlY2lvKQptYXNfYmFyYXRvIDwtIGRhdGEuZnJhbWUoTm9tYnJlID0gbWFzX2JhcmF0b3Nfbm9tYnJlLCAKICAgICAgICAgICAgICAgICAgICAgICBFcXVpcG8gPSBtYXNfYmFyYXRvc19lcXVpcG8sIAogICAgICAgICAgICAgICAgICAgICAgIFByZWNpbyA9IG1hc19iYXJhdG9zX3ByZWNpbykKCmthYmxlKG1hc19iYXJhdG8pCmBgYAoKIyBSZWdyZXNpw7NuIExpbmVhbCBTaW1wbGUKCiMjIE1vZGVsbyAiR29sZXMgc29uIGFtb3JlcyIKClByZWNpbyA9IEdvbGVzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbW9kZWxvX2NsYXNpY29fZ29sZXMgPSBsbShkYXRhID0gdHJhaW5fZGF0YSwgZm9ybXVsYSA9IHByZWNpbyB+IEdscykKc3VtbWFyeShtb2RlbG9fY2xhc2ljb19nb2xlcykKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2dwbG90KGRmLCBhZXMoeD0gR2xzLCB5PXByZWNpbykpKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfYncoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiB4LCBjb2xvcj0iZm9yZXN0Z3JlZW4iLCBzZSA9IEZBTFNFKQpgYGAKCgojIyMgRGlhZ27Ds3N0aWNvCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGF0b3NfYXVnbWVudGFkb3MgPC0gYXVnbWVudChtb2RlbG9fY2xhc2ljb19nb2xlcykKZzEgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoLmZpdHRlZCwgLnJlc2lkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdW9zIHZzIHZhbG9yZXMgcHJlZGljaG9zIikgKyAKICB0aGVtZV9idygpCmcyIDwtIGdncGxvdChkYXRvc19hdWdtZW50YWRvcywgYWVzKHNhbXBsZSA9IC5zdGQucmVzaWQpKSArCiAgc3RhdF9xcSgpICsKICBnZW9tX2FibGluZSgpICsKICBsYWJzKHRpdGxlID0gIk5vcm1hbCBRUSBwbG90IikgKyAKICB0aGVtZV9idygpCmczIDwtIGdncGxvdChkYXRvc19hdWdtZW50YWRvcywgYWVzKC5maXR0ZWQsIHNxcnQoYWJzKC5zdGQucmVzaWQpKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZSA9ICJTY2FsZS1sb2NhdGlvbiBwbG90IikKZzQgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoLmhhdCwgLnN0ZC5yZXNpZCkpICsKICBnZW9tX3ZsaW5lKHNpemUgPSAyLCBjb2xvdXIgPSAid2hpdGUiLCB4aW50ZXJjZXB0ID0gMCkgKwogIGdlb21faGxpbmUoc2l6ZSA9IDIsIGNvbG91ciA9ICJ3aGl0ZSIsIHlpbnRlcmNlcHQgPSAwKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKyAKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdWFsIHZzIGxldmVyYWdlIikKZ3JpZC5hcnJhbmdlKGcxLCBnMiwgZzMsIGc0LCBucm93PTIpCmBgYAoKIyMjIyBEaWFnbsOzc3RpY28gZGUgUmVzaWR1b3MKLSBFbCBncsOhZmljbyBkZSByZXNpZHVvcyB2cyB2YWxvcmVzIHByZWRpY2hvcyBtdWVzdHJhIHVuIHBhdHLDs24gZGUgZW1idWRvLCBpbmRpY2FuZG8gaGV0ZXJvY2VkYXN0aWNpZGFkCi0gRWwgUVEtcGxvdCBtdWVzdHJhIGRlc3ZpYWNpb25lcyBzaWduaWZpY2F0aXZhcyBkZSBsYSBub3JtYWxpZGFkLCBlc3BlY2lhbG1lbnRlIGVuIGxhcyBjb2xhcwotIEVsIFNjYWxlLWxvY2F0aW9uIHBsb3QgY29uZmlybWEgbGEgaGV0ZXJvY2VkYXN0aWNpZGFkLCBjb24gbWF5b3IgdmFyaWFiaWxpZGFkIGVuIGxvcyB2YWxvcmVzIHByZWRpY2hvcyBtw6FzIGFsdG9zCi0gRWwgZ3LDoWZpY28gZGUgbGV2ZXJhZ2UgbXVlc3RyYSB2YXJpb3MgcHVudG9zIGNvbiBhbHRhIGluZmx1ZW5jaWEgcXVlIHBvZHLDrWFuIGVzdGFyIGFmZWN0YW5kbyBlbCBtb2RlbG8KCiMjIyMgSW50ZXJwcmV0YWNpw7NuIGRlbCBNb2RlbG8KLSBFbCBtb2RlbG8gZXN0YWJsZWNlIHVuYSByZWxhY2nDs24gbGluZWFsIHNpbXBsZSBlbnRyZSBlbCBwcmVjaW8gZGVsIGp1Z2Fkb3IgeSBsb3MgZ29sZXMgYW5vdGFkb3MKLSBQb3IgY2FkYSBnb2wgYW5vdGFkbywgc2UgcHJlY2liZSB1biBhdW1lbnRvIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdm8gKHAtdmFsdWUgPCAwLjA1KSBlbiBlbCBwcmVjaW8gZGVsIGp1Z2Fkb3IgZGUg4oKsMywyNjcsMzc1Ci0gRWwgaW50ZXJjZXB0byBlcyDigqw1LDIzOCw0NTIsIHF1ZSByZXByZXNlbnRhIGVsIHByZWNpbyBiYXNlIGVzdGltYWRvIHBhcmEgdW4ganVnYWRvciBzaW4gZ29sZXMKLSBFbCBtb2RlbG8gZXMgZXN0YWTDrXN0aWNhbWVudGUgc2lnbmlmaWNhdGl2byAocC12YWx1ZSA8IDIuMmUtMTYpCi0gRWwgUsKyIGFqdXN0YWRvIGVzIDAuMjk4NSwgbG8gcXVlIGluZGljYSBxdWUgZWwgbW9kZWxvIGV4cGxpY2EgYXByb3hpbWFkYW1lbnRlIGVsIDMwJSBkZSBsYSB2YXJpYWJpbGlkYWQgZW4gbG9zIHByZWNpb3MKCiMjIyMgRXZhbHVhY2nDs24gZGVsIFJlbmRpbWllbnRvCgojIyMjIyBEYXRvcyBkZSBlbnRyZW5hbWllbnRvCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcmVkX21vZGVsb19jbGFzaWNvX2dvbGVzIDwtIGF1Z21lbnQobW9kZWxvX2NsYXNpY29fZ29sZXMsIG5ld2RhdGEgPSB0cmFpbl9kYXRhKQpjYXQoIlJNU0U6ICIsIHJtc2UoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fZ29sZXMsIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IC5maXR0ZWQpJC5lc3RpbWF0ZSkKYGBgCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjYXQoIk1BRTogIiwgbWFlKGRhdGEgPSBwcmVkX21vZGVsb19jbGFzaWNvX2dvbGVzLCB0cnV0aCA9IHByZWNpbywgZXN0aW1hdGUgPSAuZml0dGVkKSQuZXN0aW1hdGUpCmBgYAoKIyMjIyMgRGF0b3MgZGUgcHJ1ZWJhCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcmVkX21vZGVsb19jbGFzaWNvX2dvbGVzIDwtIGF1Z21lbnQobW9kZWxvX2NsYXNpY29fZ29sZXMsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpCmNhdCgiUk1TRTogIiwgcm1zZShkYXRhID0gcHJlZF9tb2RlbG9fY2xhc2ljb19nb2xlcywgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gLmZpdHRlZCkkLmVzdGltYXRlKQpgYGAKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiTUFFOiAiLCBtYWUoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fZ29sZXMsIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IC5maXR0ZWQpJC5lc3RpbWF0ZSkKYGBgCi0gRW4gZGF0b3MgZGUgZW50cmVuYW1pZW50bzoKICAtIFJNU0U6IDE1LDM4OCw4NjYKICAtIE1BRTogOSwxNzksODEzCi0gRW4gZGF0b3MgZGUgcHJ1ZWJhOgogIC0gUk1TRTogMTcsNTAzLDg2OQogIC0gTUFFOiA5LDczMiw1OTgKLSBMYSBkaWZlcmVuY2lhIHJlbGF0aXZhbWVudGUgcGVxdWXDsWEgZW50cmUgbG9zIGVycm9yZXMgZGUgZW50cmVuYW1pZW50byB5IHBydWViYSBzdWdpZXJlIHF1ZSBlbCBtb2RlbG8gbm8gZXN0w6Egc29icmVhanVzdGFkbwoKIyMjIyBMaW1pdGFjaW9uZXMgZGVsIE1vZGVsbwotIExhIHJlbGFjacOzbiBsaW5lYWwgc2ltcGxlIHB1ZWRlIHNlciBkZW1hc2lhZG8gYsOhc2ljYSBwYXJhIGNhcHR1cmFyIGxhIGNvbXBsZWppZGFkIGRlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3JlcwotIExhIHByZXNlbmNpYSBkZSBoZXRlcm9jZWRhc3RpY2lkYWQgc3VnaWVyZSBxdWUgbGEgdmFyaWFiaWxpZGFkIGRlbCBwcmVjaW8gYXVtZW50YSBjb24gZWwgbsO6bWVybyBkZSBnb2xlcwotIEVsIGJham8gUsKyIGluZGljYSBxdWUgaGF5IG90cm9zIGZhY3RvcmVzIGltcG9ydGFudGVzIHF1ZSBubyBlc3TDoW4gc2llbmRvIGNvbnNpZGVyYWRvcwotIExvcyBzdXB1ZXN0b3MgZGUgbm9ybWFsaWRhZCB5IGhvbW9jZWRhc3RpY2lkYWQgbm8gc2UgY3VtcGxlbiBhZGVjdWFkYW1lbnRlCgpFc3RlIG1vZGVsbywgYXVucXVlIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdm8sIHRpZW5lIGxpbWl0YWNpb25lcyBpbXBvcnRhbnRlcyBwYXJhIHByZWRlY2lyIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzLiBMYSB2aW9sYWNpw7NuIGRlIGxvcyBzdXB1ZXN0b3MgYsOhc2ljb3MgeSBlbCBiYWpvIHBvZGVyIGV4cGxpY2F0aXZvIHN1Z2llcmVuIHF1ZSBzZSBuZWNlc2l0YSB1biBtb2RlbG8gbcOhcyBjb21wbGVqbyBxdWUgaW5jb3Jwb3JlIHZhcmlhYmxlcyBhZGljaW9uYWxlcyB5IHBvc2libGVtZW50ZSB0cmFuc2Zvcm1hY2lvbmVzIGRlIGxhcyB2YXJpYWJsZXMgZXhpc3RlbnRlcy4KCiMjIE1vZGVsbyAiTGEgZWRhZCBOTyBlcyBsbyBkZSBtZW5vcyIKClByZWNpbyA9ICRFZGFkJCArICRFZGFkXjIkCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbW9kZWxvX2NsYXNpY29fZWRhZCA9IG1vZGVsb19jbGFzaWNvX2VkYWQyID0gbG0oZGF0YSA9IHRyYWluX2RhdGEsIGZvcm11bGEgPSBwcmVjaW8gfiBBZ2UgKyBJKEFnZV4yKSkKc3VtbWFyeShtb2RlbG9fY2xhc2ljb19lZGFkKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3QoZGYsIGFlcyh4PSBBZ2UsIHk9cHJlY2lvKSkrCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9idygpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHggKyBJKHheMiksIGNvbG9yPSJmb3Jlc3RncmVlbiIsIHNlID0gRkFMU0UpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiQSBwYXJ0aXIgZGUgbG9zIiwgcm91bmQoNTExMzYxNS8oMioxMTAxNDcpLDApLCAiYcOxb3MsIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzIGNvbWllbnphIGEgZGlzbWludWlyIikKYGBgCgojIyMgRGlhZ27Ds3N0aWNvCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGF0b3NfYXVnbWVudGFkb3MgPC0gYXVnbWVudChtb2RlbG9fY2xhc2ljb19lZGFkKQpnMSA8LSBnZ3Bsb3QoZGF0b3NfYXVnbWVudGFkb3MsIGFlcyguZml0dGVkLCAucmVzaWQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwKSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKwogIGxhYnModGl0bGUgPSAiUmVzaWR1b3MgdnMgdmFsb3JlcyBwcmVkaWNob3MiKSArIAogIHRoZW1lX2J3KCkKZzIgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoc2FtcGxlID0gLnN0ZC5yZXNpZCkpICsKICBzdGF0X3FxKCkgKwogIGdlb21fYWJsaW5lKCkgKwogIGxhYnModGl0bGUgPSAiTm9ybWFsIFFRIHBsb3QiKSArIAogIHRoZW1lX2J3KCkKZzMgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoLmZpdHRlZCwgc3FydChhYnMoLnN0ZC5yZXNpZCkpKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKyAKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlID0gIlNjYWxlLWxvY2F0aW9uIHBsb3QiKQpnNCA8LSBnZ3Bsb3QoZGF0b3NfYXVnbWVudGFkb3MsIGFlcyguaGF0LCAuc3RkLnJlc2lkKSkgKwogIGdlb21fdmxpbmUoc2l6ZSA9IDIsIGNvbG91ciA9ICJ3aGl0ZSIsIHhpbnRlcmNlcHQgPSAwKSArCiAgZ2VvbV9obGluZShzaXplID0gMiwgY29sb3VyID0gIndoaXRlIiwgeWludGVyY2VwdCA9IDApICsKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArIAogIHRoZW1lX2J3KCkgKwogIGxhYnModGl0bGUgPSAiUmVzaWR1YWwgdnMgbGV2ZXJhZ2UiKQpncmlkLmFycmFuZ2UoZzEsIGcyLCBnMywgZzQsIG5yb3c9MikKYGBgCgojIyMjIERpYWduw7NzdGljbyBkZSBSZXNpZHVvcwotIEVsIGdyw6FmaWNvIGRlIHJlc2lkdW9zIHZzIHZhbG9yZXMgcHJlZGljaG9zIG11ZXN0cmEgdW4gcGF0csOzbiBkZSBlbWJ1ZG8gc2ltaWxhciBhbCBtb2RlbG8gYW50ZXJpb3IKLSBFbCBRUS1wbG90IG11ZXN0cmEgZGVzdmlhY2lvbmVzIHNpZ25pZmljYXRpdmFzIGRlIGxhIG5vcm1hbGlkYWQsIGVzcGVjaWFsbWVudGUgZW4gbGFzIGNvbGFzIHN1cGVyaW9yZXMKLSBFbCBTY2FsZS1sb2NhdGlvbiBwbG90IGluZGljYSBoZXRlcm9jZWRhc3RpY2lkYWQKLSBFbCBncsOhZmljbyBkZSBsZXZlcmFnZSBtdWVzdHJhIGFsZ3Vub3MgcHVudG9zIGluZmx1eWVudGVzLCBhdW5xdWUgbWVub3MgcHJvbnVuY2lhZG9zIHF1ZSBlbiBlbCBtb2RlbG8gZGUgZ29sZXMKCiMjIyMgSW50ZXJwcmV0YWNpw7NuIGRlbCBNb2RlbG8KLSBFbCBtb2RlbG8gZXN0YWJsZWNlIHVuYSByZWxhY2nDs24gY3VhZHLDoXRpY2EgZW50cmUgZWwgcHJlY2lvIGRlbCBqdWdhZG9yIHkgc3UgZWRhZAotIEVsIHByZWNpbyBhdW1lbnRhIGNvbiBsYSBlZGFkIGhhc3RhIGxvcyAyMyBhw7FvcywgcHVudG8gYSBwYXJ0aXIgZGVsIGN1YWwgY29taWVuemEgYSBkaXNtaW51aXIKLSBMb3MgY29lZmljaWVudGVzIHNvbjoKICAtIEVkYWQ6ICs1LDExMyw2MTUgKGVmZWN0byBsaW5lYWwgcG9zaXRpdm8pCiAgLSBFZGFkwrI6IC0xMTAsMTQ3IChlZmVjdG8gY3VhZHLDoXRpY28gbmVnYXRpdm8pCiAgLSBJbnRlcmNlcHRvOiAtNDUsNTQ3LDMwNQotIEVsIG1vZGVsbyBlcyBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZvIChwLXZhbHVlOiAxLjI1NWUtMTMpCi0gRWwgUsKyIGFqdXN0YWRvIGVzIDAuMDQ4NTMsIGxvIHF1ZSBpbmRpY2EgcXVlIGVsIG1vZGVsbyBleHBsaWNhIHNvbG8gYXByb3hpbWFkYW1lbnRlIGVsIDUlIGRlIGxhIHZhcmlhYmlsaWRhZCBlbiBsb3MgcHJlY2lvcwoKIyMjIyBFdmFsdWFjacOzbiBkZWwgUmVuZGltaWVudG8KCiMjIyMjIERhdG9zIGRlIGVudHJlbmFtaWVudG8KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnByZWRfbW9kZWxvX2NsYXNpY29fZWRhZCA8LSBhdWdtZW50KG1vZGVsb19jbGFzaWNvX2VkYWQsIG5ld2RhdGEgPSB0cmFpbl9kYXRhKQpjYXQoIlJNU0U6ICIsIHJtc2UoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fZWRhZCwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gLmZpdHRlZCkkLmVzdGltYXRlKQpgYGAKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiTUFFOiAiLCBtYWUoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fZWRhZCwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gLmZpdHRlZCkkLmVzdGltYXRlKQpgYGAKCiMjIyMjIERhdG9zIGRlIHBydWViYQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHJlZF9tb2RlbG9fY2xhc2ljb19lZGFkIDwtIGF1Z21lbnQobW9kZWxvX2NsYXNpY29fZWRhZCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKY2F0KCJSTVNFOiAiLCBybXNlKGRhdGEgPSBwcmVkX21vZGVsb19jbGFzaWNvX2VkYWQsIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IC5maXR0ZWQpJC5lc3RpbWF0ZSkKYGBgCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjYXQoIk1BRTogIiwgbWFlKGRhdGEgPSBwcmVkX21vZGVsb19jbGFzaWNvX2VkYWQsIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IC5maXR0ZWQpJC5lc3RpbWF0ZSkKYGBgCi0gRW4gZGF0b3MgZGUgZW50cmVuYW1pZW50bzoKICAtIFJNU0U6IDE3LDkxNCw2MjcKICAtIE1BRTogMTAsNTE5LDEyNgotIEVuIGRhdG9zIGRlIHBydWViYToKICAtIFJNU0U6IDE4LDQ4Miw2ODMKICAtIE1BRTogMTAsNjU1LDg0OAotIExvcyBlcnJvcmVzIHNvbiBtYXlvcmVzIHF1ZSBlbiBlbCBtb2RlbG8gZGUgZ29sZXMsIHN1Z2lyaWVuZG8gcXVlIGxhIGVkYWQgcG9yIHPDrSBzb2xhIGVzIHVuIHByZWRpY3RvciBtw6FzIGTDqWJpbCBkZWwgcHJlY2lvCgojIyMjIExpbWl0YWNpb25lcyBkZWwgTW9kZWxvCi0gRWwgbXV5IGJham8gUsKyIHN1Z2llcmUgcXVlIGxhIGVkYWQgcG9yIHPDrSBzb2xhIG5vIGVzIHVuIGJ1ZW4gcHJlZGljdG9yIGRlbCBwcmVjaW8KLSBMYSByZWxhY2nDs24gY3VhZHLDoXRpY2EgY2FwdHVyYSBlbCBoZWNobyBkZSBxdWUgbG9zIGp1Z2Fkb3JlcyBhbGNhbnphbiB1biBwaWNvIGRlIHZhbG9yLCBwZXJvIGVsIGFqdXN0ZSBnZW5lcmFsIGVzIHBvYnJlCi0gTG9zIHN1cHVlc3RvcyBkZSBub3JtYWxpZGFkIHkgaG9tb2NlZGFzdGljaWRhZCBzaWd1ZW4gc2luIGN1bXBsaXJzZQotIEVsIG1vZGVsbyBubyBjYXB0dXJhIG90cm9zIGZhY3RvcmVzIGltcG9ydGFudGVzIHF1ZSBhZmVjdGFuIGVsIHByZWNpbwoKRXN0ZSBtb2RlbG8gY29uZmlybWEgcXVlIGV4aXN0ZSB1bmEgcmVsYWNpw7NuIG5vIGxpbmVhbCBlbnRyZSBsYSBlZGFkIHkgZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMsIGNvbiB1biBwdW50byBtw6F4aW1vIGFscmVkZWRvciBkZSBsb3MgMjMgYcOxb3MuIFNpbiBlbWJhcmdvLCBzdSBiYWpvIHBvZGVyIGV4cGxpY2F0aXZvIHN1Z2llcmUgcXVlIGxhIGVkYWQgZGViZSBjb21iaW5hcnNlIGNvbiBvdHJhcyB2YXJpYWJsZXMgcGFyYSBvYnRlbmVyIHByZWRpY2Npb25lcyBtw6FzIHByZWNpc2FzLgoKCiMgUmVncmVzacOzbiBMaW5lYWwgTcO6bHRpcGxlCgojIyBNb2RlbG8gIkFob3JhIHZhIGVuIHNlcmlvIgoKJFByZWNpbyQgPSAkR29sZXMkICsgJEVkYWQkICsgJEVkYWReMiQgKyAkQXNpc3RlbmNpYXMkICsgJENvbnRpbmVudGUkICRkZSQgJG5hY2ltaWVudG8kICRkZWwkICRqdWdhZG9yJCArICRMaWdhJCAkZG9uZGUkICRqdWVnYSQKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptb2RlbG9fY2xhc2ljb19tdWx0aXBsZV8xID0gbG0oZGF0YSA9IHRyYWluX2RhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm11bGEgPSBwcmVjaW8gfiBHbHMgKyBBZ2UgKyBJKEFnZV4yKSArIEFzdCArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGluZW50ZSArIGN1cnJlbnRfY2x1Yl9kb21lc3RpY19jb21wZXRpdGlvbl9pZCkKc3VtbWFyeShtb2RlbG9fY2xhc2ljb19tdWx0aXBsZV8xKQpgYGAKCiMjIyBEaWFnbsOzc3RpY28KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkYXRvc19hdWdtZW50YWRvcyA8LSBhdWdtZW50KG1vZGVsb19jbGFzaWNvX211bHRpcGxlXzEpCmc1IDwtIGdncGxvdChkYXRvc19hdWdtZW50YWRvcywgYWVzKC5maXR0ZWQsIC5yZXNpZCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgbGFicyh0aXRsZSA9ICJSZXNpZHVvcyB2cyB2YWxvcmVzIHByZWRpY2hvcyIpICsgCiAgdGhlbWVfYncoKQpnNiA8LSBnZ3Bsb3QoZGF0b3NfYXVnbWVudGFkb3MsIGFlcyhzYW1wbGUgPSAuc3RkLnJlc2lkKSkgKwogIHN0YXRfcXEoKSArCiAgZ2VvbV9hYmxpbmUoKSArCiAgbGFicyh0aXRsZSA9ICJOb3JtYWwgUVEgcGxvdCIpICsgCiAgdGhlbWVfYncoKQpnNyA8LSBnZ3Bsb3QoZGF0b3NfYXVnbWVudGFkb3MsIGFlcyguZml0dGVkLCBzcXJ0KGFicyguc3RkLnJlc2lkKSkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArIAogIHRoZW1lX2J3KCkgKwogIGxhYnModGl0bGUgPSAiU2NhbGUtbG9jYXRpb24gcGxvdCIpCmc4IDwtIGdncGxvdChkYXRvc19hdWdtZW50YWRvcywgYWVzKC5oYXQsIC5zdGQucmVzaWQpKSArCiAgZ2VvbV92bGluZShzaXplID0gMiwgY29sb3VyID0gIndoaXRlIiwgeGludGVyY2VwdCA9IDApICsKICBnZW9tX2hsaW5lKHNpemUgPSAyLCBjb2xvdXIgPSAid2hpdGUiLCB5aW50ZXJjZXB0ID0gMCkgKwogIGdlb21fcG9pbnQoKSArIAogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZSA9ICJSZXNpZHVhbCB2cyBsZXZlcmFnZSIpCmdyaWQuYXJyYW5nZShnNSwgZzYsIGc3LCBnOCwgbnJvdz0yKQpgYGAKCiMjIyMgRGlhZ27Ds3N0aWNvIGRlIFJlc2lkdW9zCi0gRWwgZ3LDoWZpY28gZGUgcmVzaWR1b3MgdnMgdmFsb3JlcyBwcmVkaWNob3MgbXVlc3RyYSB1bmEgbWVqb3JhIGVuIGVsIHBhdHLDs24gZGUgaGV0ZXJvY2VkYXN0aWNpZGFkCi0gRWwgUVEtcGxvdCBpbmRpY2EgdW5hIG1lam9yIGFwcm94aW1hY2nDs24gYSBsYSBub3JtYWxpZGFkIGVuIGVsIGNlbnRybyBkZSBsYSBkaXN0cmlidWNpw7NuCi0gRWwgU2NhbGUtbG9jYXRpb24gcGxvdCBtdWVzdHJhIHVuYSB2YXJpYWJpbGlkYWQgbcOhcyBlc3RhYmxlIHF1ZSBsb3MgbW9kZWxvcyBhbnRlcmlvcmVzCi0gRWwgZ3LDoWZpY28gZGUgbGV2ZXJhZ2UgaWRlbnRpZmljYSBtZW5vcyBwdW50b3MgaW5mbHV5ZW50ZXMgZXh0cmVtb3MKCiMjIyMgSW50ZXJwcmV0YWNpw7NuIGRlbCBNb2RlbG8KLSBFbCBtb2RlbG8gaW5jb3Jwb3JhIG3Dumx0aXBsZXMgdmFyaWFibGVzIHByZWRpY3RvcmFzOiBnb2xlcywgZWRhZCAobGluZWFsIHkgY3VhZHLDoXRpY2EpLCBhc2lzdGVuY2lhcywgY29udGluZW50ZSBkZSBvcmlnZW4geSBsaWdhCi0gTG9zIGNvZWZpY2llbnRlcyBtw6FzIHNpZ25pZmljYXRpdm9zIHNvbjoKICAtIEdvbGVzOiArMiwxODksODU1IHBvciBnb2wgKHAtdmFsdWUgPCAyZS0xNikuIEVzdG8gaW5kaWNhIHF1ZSBlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3JlcyBxdWUgdGllbmVuIG1pc21hIGNhbnRpZGFkIGRlIGFzaXN0ZW5jaWFzIHkgZWRhZCBhdW1lbnRhIOKCrDIsMTg5LDg1NSBwb3IgY2FkYSBnb2wgYW5vdGFkbwogIC0gQXNpc3RlbmNpYXM6ICsyLDE1Miw2MDcgcG9yIGFzaXN0ZW5jaWEgKHAtdmFsdWUgPCAyZS0xNikuIEVzdG8gaW5kaWNhIHF1ZSBlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3JlcyBxdWUgdGllbmVuIG1pc21hIGNhbnRpZGFkIGRlIGdvbGVzIHkgZWRhZCBhdW1lbnRhIOKCrDIsMTUyLDYwNyBwb3IgY2FkYSBhc2lzdGVuY2lhIGFkaWNpb25hbC4KICAtIEVkYWQ6IGVmZWN0byBjdWFkcsOhdGljbyBjb24gbcOheGltbyBhbHJlZGVkb3IgZGUgbG9zIDIzIGHDsW9zCiAgLSBBbcOpcmljYTogKzQsNDk0LDExMyByZXNwZWN0byBhIMOBZnJpY2EgKHAtdmFsdWUgPSAwLjAxNSkKICAtIFByZW1pZXIgTGVhZ3VlOiArMTEsMDYwLDExNiByZXNwZWN0byBhIGxhIGxpZ2EgZGUgcmVmZXJlbmNpYSwgTGEgTGlnYSAoRXNwYcOxYSkgKHAtdmFsdWUgPCAyZS0xNikKICAtIExpZ3VlIDE6IC00LDQzNiw5MTcgcmVzcGVjdG8gYSBsYSBsaWdhIGRlIHJlZmVyZW5jaWEsIExhIExpZ2EgKHAtdmFsdWUgPCAwLjA1KQogIC0gQnVuZGVzbGlnYTogLTQsMDExLDgwNiByZXNwZWN0byBhIGxhIGxpZ2EgZGUgcmVmZXJlbmNpYSwgTGEgTGlnYSAocC12YWx1ZSA8IDAuMDUpCiAgLSBTZXJpZSBBIChJdGFsaWEpOiAtMiw0MjksMDYwIHJlc3BlY3RvIGEgbGEgbGlnYSBkZSByZWZlcmVuY2lhLCBMYSBMaWdhIChwLXZhbHVlIDwgMC4wNSkKLSBFbCBSwrIgYWp1c3RhZG8gZXMgMC40NzMzLCBpbmRpY2FuZG8gcXVlIGVsIG1vZGVsbyBleHBsaWNhIGFwcm94aW1hZGFtZW50ZSBlbCA0NyUgZGUgbGEgdmFyaWFiaWxpZGFkCgojIyMjIEV2YWx1YWNpw7NuIGRlbCBSZW5kaW1pZW50bwoKIyMjIyMgRGF0b3MgZGUgZW50cmVuYW1pZW50bwpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZV8xIDwtIGF1Z21lbnQobW9kZWxvX2NsYXNpY29fbXVsdGlwbGVfMSwgbmV3ZGF0YSA9IHRyYWluX2RhdGEpCmNhdCgiUk1TRTogIiwgcm1zZShkYXRhID0gcHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZV8xLCB0cnV0aCA9IHByZWNpbywgZXN0aW1hdGUgPSAuZml0dGVkKSQuZXN0aW1hdGUpCmBgYApgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KY2F0KCJNQUU6ICIsIG1hZShkYXRhID0gcHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZV8xLCB0cnV0aCA9IHByZWNpbywgZXN0aW1hdGUgPSAuZml0dGVkKSQuZXN0aW1hdGUpCmBgYAoKIyMjIyMgRGF0b3MgZGUgcHJ1ZWJhCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcmVkX21vZGVsb19jbGFzaWNvX211bHRpcGxlXzEgPC0gYXVnbWVudChtb2RlbG9fY2xhc2ljb19tdWx0aXBsZV8xLCBuZXdkYXRhID0gdGVzdF9kYXRhKQpjYXQoIlJNU0U6ICIsIHJtc2UoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGVfMSwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gLmZpdHRlZCkkLmVzdGltYXRlKQpgYGAKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiTUFFOiAiLCBtYWUoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGVfMSwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gLmZpdHRlZCkkLmVzdGltYXRlKQpgYGAKLSBFbiBkYXRvcyBkZSBlbnRyZW5hbWllbnRvOgogIC0gUk1TRTogMTMsMjc2LDkxMwogIC0gTUFFOiA4LDA5MywyODAKLSBFbiBkYXRvcyBkZSBwcnVlYmE6CiAgLSBSTVNFOiAxNSw0NTAsMTgwCiAgLSBNQUU6IDgsNDY5LDkwNgotIExhIGRpZmVyZW5jaWEgbW9kZXJhZGEgZW50cmUgZXJyb3JlcyBkZSBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhIHN1Z2llcmUgdW4gbml2ZWwgYWNlcHRhYmxlIGRlIGdlbmVyYWxpemFjacOzbgoKIyMjIyBNZWpvcmFzIFJlc3BlY3RvIGEgTW9kZWxvcyBBbnRlcmlvcmVzCi0gRWwgUsKyIGFqdXN0YWRvIGF1bWVudMOzIHNpZ25pZmljYXRpdmFtZW50ZSAoZGUgMC4zMCB5IDAuMDUgYSAwLjQ3KQotIExvcyBlcnJvcmVzIGRlIHByZWRpY2Npw7NuIChSTVNFIHkgTUFFKSBkaXNtaW51eWVyb24KLSBMb3MgZGlhZ27Ds3N0aWNvcyBkZSByZXNpZHVvcyBtdWVzdHJhbiBtZWpvcmVzIHByb3BpZWRhZGVzIGVzdGFkw61zdGljYXMKLSBMYSBpbmNvcnBvcmFjacOzbiBkZSB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIGNhcHR1cmEgZWZlY3RvcyBlc3BlY8OtZmljb3MgcG9yIGNvbnRpbmVudGUgeSBsaWdhCgpFc3RlIG1vZGVsbyByZXByZXNlbnRhIHVuYSBtZWpvcmEgc3VzdGFuY2lhbCBzb2JyZSBsb3MgbW9kZWxvcyBzaW1wbGVzIGFudGVyaW9yZXMsIGNhcHR1cmFuZG8gZWZlY3RvcyBtw6FzIGNvbXBsZWpvcyB5IHJlZHVjaWVuZG8gbG9zIGVycm9yZXMgZGUgcHJlZGljY2nDs24uIFNpbiBlbWJhcmdvLCBhw7puIGhheSBlc3BhY2lvIHBhcmEgbWVqb3JhcywgZXNwZWNpYWxtZW50ZSBlbiBlbCB0cmF0YW1pZW50byBkZSB2YWxvcmVzIGV4dHJlbW9zIHkgbGEgcG9zaWJsZSBpbmNvcnBvcmFjacOzbiBkZSBtw6FzIHZhcmlhYmxlcyByZWxldmFudGVzLgoKCiMjIE1vZGVsbyAiQWhvcmEgdmEgZW4gc2VyaW8gMiIKCiRsb2coUHJlY2lvKSQgPSAkR29sZXMkICsgJEVkYWQkICsgJEVkYWReMiQgKyAkQXNpc3RlbmNpYXMkICsgJENvbnRpbmVudGUkICRkZSQgJG5hY2ltaWVudG8kICRkZWwkICRqdWdhZG9yJCArICRMaWdhJCAkZG9uZGUkICRqdWVnYSQKCkFsIHV0aWxpemFyIHVuYSB0cmFuc2Zvcm1hY2nDs24gbG9nYXLDrXRtaWNhLCBsYSBpbnRlcnByZXRhY2nDs24gZGVsIG1vZGVsbyBlcyBkaWZlcmVudGUsIHlhIHF1ZSBzZSBpbnRyb2R1Y2UgdW5hIHJlbGFjacOzbiBkZSBzZW1pZWxhc3RpY2lkYWQgZW50cmUgbGFzIHZhcmlhYmxlcy4gTGEgdmFyaWFjacOzbiBkZSBsYSB2YXJpYWJsZSBhIHByZWRlY2lyIGVzIGVuIHTDqXJtaW5vcyBwb3JjZW50dWFsZXMgc2Vnw7puIGF1bWVudGEgZW4gdW5hIHVuaWRhZCBsYSB2YXJpYWJsZSBwcmVkaWN0b3JhIG51bcOpcmljYSBvIHNlZ8O6biBsYSB2YXJpYWJsZSBkZSByZWZlcmVuY2lhIGNhdGVnw7NyaWNhLgoKUG9yIGxvIHRhbnRvOgoKJCQgCmMgPSAoXGV4cChiKSAtIDEpIFx0aW1lcyAxMDAgXHF1YWQgXHRleHR7KDEzKX0KJCQgCgpEb25kZQoKJCQgCnkgPSBhICsgYiBcdGltZXMgeCBccXVhZCBcdGV4dHsoMTQpfQokJCAKCkVuIGVzdGUgY2Fzbywgc2kgJHgkIGVzIHVuYSB2YXJpYWJsZSBudW3DqXJpY2EgZW50b25jZXMgJHkkIHZhcsOtYSAkYyQlIHBvciBjYWRhIGF1bWVudG8gZGUgJHgkIGVuIHVuYSB1bmlkYWQuIFNpIGVuIGNhbWJpbyAkeCQgZXMgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhLCAkeSQgdmFyw61hICRjJCUgY29uIHJlc3BlY3RvIGEgbGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgZGUgcmVmZXJlbmNpYS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptb2RlbG9fY2xhc2ljb19tdWx0aXBsZSA9IGxtKGRhdGEgPSB0cmFpbl9kYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtdWxhID0gbG9nKHByZWNpbykgfiBHbHMgKyBBZ2UgKyBJKEFnZV4yKSArIEFzdCArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGluZW50ZSArIGN1cnJlbnRfY2x1Yl9kb21lc3RpY19jb21wZXRpdGlvbl9pZCkKc3VtbWFyeShtb2RlbG9fY2xhc2ljb19tdWx0aXBsZSkKYGBgCgoKIyMjIERpYWduw7NzdGljbwoKTG9zIHNpZ3VpZW50ZXMgaGlzdG9ncmFtYXMgbXVlc3RyYW4gY29tbyBtZWpvcmEgbGEgZGl0cmlidWNpw7NuIGRlIGxvcyBwcmVjaW9zIGFwbGljYW5kbyBsYSBkaXN0cmlidWNpw7NuIGxvZ3JhcsOtdG1pY2EgbG9ncmFuZG8gdW5hIGFwcm94aW1hY2nDs24gYSBsb3Mgbm9ybWFsaWRhZCB5IGhvbW9jZWRhc3RpY2lkYWQgZGUgbG9zIGRhdG9zIGEgcHJlZGVjaXIuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KaGlzdDEgPC0gZ2dwbG90KGRmLCBhZXMoeD1wcmVjaW8pKSsKICBnZW9tX2hpc3RvZ3JhbSgpICsKICBsYWJzKHRpdGxlID0gIkhpc3RvZ3JhbWEgZGVsIFByZWNpbyIpICsgCiAgdGhlbWVfYncoKQpoaXN0MiA8LSBnZ3Bsb3QoZGYsIGFlcyh4PWxvZyhwcmVjaW8pKSkrCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW1hIGRlbCBsb2coUHJlY2lvKSIpICsgCiAgdGhlbWVfYncoKQpncmlkLmFycmFuZ2UoIGhpc3QxLCBoaXN0MiwgbnJvdz0xKQpgYGAKCkVuIGVzdGUgY2FzbywgZWwgdmFsb3IgZGUgJFJeMiQgYWp1c3RhZG8gZGVsIG1vZGVsbyBlcyBjb24gcmVzcGVjdG8gYSBsYSB2YXJpYWJsZSB0cmFuc2Zvcm1hZGEsIHBvciBsbyB0YW50byB0ZW5lbW9zIHF1ZSBwcmVkZWNpciB0b2RvcyBsb3MgZGF0b3MgdXRpbGl6YWRvcyBlbiBlbCBtb2RlbG8geSB0cmFzZm9ybWFybG9zIGV4cG9uZW5jaWFsbWVudGUgcGFyYSBvYnRlbmVyIGVsIHZhbG9yIHJlYWwgZGVsIHNhbGFyaW8gcG9yIGhvcmEgZXNwZXJhZG8uCgpGaW5hbG1lbnRlLCBzZSBjYWxjdWxhIGVsIHZhbG9yIGRlICRSXjIkIGFqdXN0YWRvIOKAnG1hbnVhbG1lbnRl4oCdLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGUgPC0gYXVnbWVudChtb2RlbG9fY2xhc2ljb19tdWx0aXBsZSwgbmV3ZGF0YSA9IHRyYWluX2RhdGEpCnByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGUkZXhwX2ZpdHRlZCA8LSBleHAocHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZSQuZml0dGVkKQptZXRyaWNhczEgPSBtZXRyaWNzKGRhdGEgPSBwcmVkX21vZGVsb19jbGFzaWNvX211bHRpcGxlLAogICAgICAgICAgICAgICAgICAgIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IGV4cF9maXR0ZWQpICU+JSAKICBtdXRhdGUoLmVzdGltYXRlID0gcm91bmQoLmVzdGltYXRlLCA0KSkKI3JjdWFkcmFkbyA8LSBtZXRyaWNhczEkLmVzdGltYXRlWzJdCiNjYXQoIlItY3VhZHJhZG8gPSAiLCByY3VhZHJhZG8pCiNtZXRyaWNhczEKIyBDYWxjdWxhciBlbCBSwrIgYWp1c3RhZG8gbWFudWFsbWVudGUKbiA8LSBucm93KHByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGUpICAjIE7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcwpwIDwtIGxlbmd0aChjb2VmKG1vZGVsb19jbGFzaWNvX211bHRpcGxlKSkgLSAxICAjIE7Dum1lcm8gZGUgcHJlZGljdG9yZXMgKHJlc3RhbW9zIDEgcG9yIGVsIGludGVyY2VwdG8pCgpyMiA8LSBtZXRyaWNhczEgJT4lIGZpbHRlcigubWV0cmljID09ICJyc3EiKSAlPiUgcHVsbCguZXN0aW1hdGUpCnIyX2FqdXN0YWRvIDwtIDEgLSAoKDEgLSByMikgKiAobiAtIDEpIC8gKG4gLSBwIC0gMSkpCgojIE1vc3RyYXIgZWwgUsKyIGFqdXN0YWRvCmNhdCgiUl4yIGFqdXN0YWRvID0gIiwgcjJfYWp1c3RhZG8pCmBgYAoKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGF0b3NfYXVnbWVudGFkb3MgPC0gYXVnbWVudChtb2RlbG9fY2xhc2ljb19tdWx0aXBsZSkKZzkgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoLmZpdHRlZCwgLnJlc2lkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdW9zIHZzIHZhbG9yZXMgcHJlZGljaG9zIikgKyAKICB0aGVtZV9idygpCmcxMCA8LSBnZ3Bsb3QoZGF0b3NfYXVnbWVudGFkb3MsIGFlcyhzYW1wbGUgPSAuc3RkLnJlc2lkKSkgKwogIHN0YXRfcXEoKSArCiAgZ2VvbV9hYmxpbmUoKSArCiAgbGFicyh0aXRsZSA9ICJOb3JtYWwgUVEgcGxvdCIpICsgCiAgdGhlbWVfYncoKQpnMTEgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoLmZpdHRlZCwgc3FydChhYnMoLnN0ZC5yZXNpZCkpKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKyAKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlID0gIlNjYWxlLWxvY2F0aW9uIHBsb3QiKQpnMTIgPC0gZ2dwbG90KGRhdG9zX2F1Z21lbnRhZG9zLCBhZXMoLmhhdCwgLnN0ZC5yZXNpZCkpICsKICBnZW9tX3ZsaW5lKHNpemUgPSAyLCBjb2xvdXIgPSAid2hpdGUiLCB4aW50ZXJjZXB0ID0gMCkgKwogIGdlb21faGxpbmUoc2l6ZSA9IDIsIGNvbG91ciA9ICJ3aGl0ZSIsIHlpbnRlcmNlcHQgPSAwKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKyAKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdWFsIHZzIGxldmVyYWdlIikKZ3JpZC5hcnJhbmdlKGc5LCBnMTAsIGcxMSwgZzEyLCBucm93PTIpCmBgYAoKIyMjIyBEaWFnbsOzc3RpY28gZGUgUmVzaWR1b3MKLSBFbCBncsOhZmljbyBkZSByZXNpZHVvcyB2cyB2YWxvcmVzIHByZWRpY2hvcyBtdWVzdHJhIHVuYSBkaXN0cmlidWNpw7NuIG3DoXMgaG9tb2fDqW5lYQotIEVsIFFRLXBsb3QgaW5kaWNhIHVuYSBub3RhYmxlIG1lam9yYSBlbiBsYSBub3JtYWxpZGFkIGRlIGxvcyByZXNpZHVvcwotIEVsIFNjYWxlLWxvY2F0aW9uIHBsb3QgbXVlc3RyYSB1bmEgdmFyaWFuemEgbcOhcyBlc3RhYmxlCi0gRWwgZ3LDoWZpY28gZGUgbGV2ZXJhZ2Ugc3VnaWVyZSBtZW5vcyBpbmZsdWVuY2lhIGRlIHZhbG9yZXMgZXh0cmVtb3MKCiMjIyMgSW50ZXJwcmV0YWNpw7NuIGRlbCBNb2RlbG8KLSBFbCBtb2RlbG8gdXRpbGl6YSBsYSB0cmFuc2Zvcm1hY2nDs24gbG9nYXLDrXRtaWNhIGRlIGxhIHZhcmlhYmxlIHByZWNpbyB5IG1hbnRpZW5lIGxhcyBtaXNtYXMgdmFyaWFibGVzIHByZWRpY3RvcmFzCi0gTG9zIGNvZWZpY2llbnRlcyBtw6FzIHNpZ25pZmljYXRpdm9zIHNvbjoKICAtIEdvbGVzOiArMC4xMTYgKHAtdmFsdWUgPCAyZS0xNikuRXN0byBpbmRpY2EgcXVlIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzIHF1ZSB0aWVuZW4gbWlzbWEgY2FudGlkYWQgZGUgYXNpc3RlbmNpYXMgeSBlZGFkIGF1bWVudGEgdW4gMTIuMyUgcG9yIGNhZGEgZ29sIGFub3RhZG8uCiAgLSBBc2lzdGVuY2lhczogKzAuMTczIChwLXZhbHVlIDwgMmUtMTYpLiBFc3RvIGluZGljYSBxdWUgZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMgcXVlIHRpZW5lbiBtaXNtYSBjYW50aWRhZCBkZSBnb2xlcyB5IGVkYWQgYXVtZW50YSB1biAxOC45JSBwb3IgY2FkYSBhc2lzdGVuY2lhIGFkaWNpb25hbC4KICAtIEVkYWQ6IGVmZWN0byBjdWFkcsOhdGljbyBzaWduaWZpY2F0aXZvIChwLXZhbHVlIDwgMmUtMTYpCiAgLSBBbcOpcmljYTogKzAuNDA0IHJlc3BlY3RvIGEgw4FmcmljYSAocC12YWx1ZSA9IDAuMDA3KSwgaW5kaWNhIHVuIGF1bWVudG8gZXN0YWTDrXN0aWNhbWV0ZSBzaWduaWZpY2F0aXZvIGRlIDQ5LjglIGVuIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzIGFtZXJpY2Fub3MgY29uIHJlc3BlY3RvIGEgbG9zIGFmcmljYW5vcy4KICAtIFByZW1pZXIgTGVhZ3VlOiArMC44MTggcmVzcGVjdG8gYSBsYSBsaWdhIGRlIHJlZmVyZW5jaWEsIChwLXZhbHVlIDwgMmUtMTYpIGluZGljYSB1biBhdW1lbnRvIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdm8gZGUgMTI2LjUlIGVuIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzIGRlIGxhIFByZW1pZXIgTGVhZ3VlIGNvbiByZXNwZWN0byBhIGxvcyBqdWdhZG9yZXMgZGUgTGEgTGlnYSAoRXNwYcOxYSkKICAtIExpZ3VlIDEgKEZyYW5jaWEpOiAtMC4yOTYgcmVzcGVjdG8gYSBsYSBsaWdhIGRlIHJlZmVyZW5jaWEsIChwLXZhbHVlID0gMC4wMDMpIGluZGljYSB1bmEgZGlzbWludWNpw7NuIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmEgZGUgMjUuNiUgZW4gZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMgZGUgbGEgTGlndWUgMSBjb24gcmVzcGVjdG8gYSBsb3MganVnYWRvcmVzIGRlIExhIExpZ2EuCiAgLSBCdW5kZXNsaWdhOiAtMC4yODUgcmVzcGVjdG8gZGUgbGEgbGlnYSBkZSByZWZlcmVuY2lhIChwLXZhbHVlID0gMC4wMDUpIGluZGljYSB1bmEgZGlzbWludWNpw7NuIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmEgZGUgMjQuOCUgZW4gZWwgcHJlaWNpbyBkZSBsb3MganVnYWRvcmVzIGRlIGxhIEJ1bmRlc2xpZ2EgY29uIHJlc3BlY3RvIGEgTGEgTGlnYS4KLSBFbCBSwrIgYWp1c3RhZG8gZXMgMC4yOSwgbWVub3IgYWwgbW9kZWxvIGFudGVyaW9yIHBlcm8gY29uIG1lam9yIGludGVycHJldGFiaWxpZGFkIHkgZGlhZ27Ds3N0aWNvLgoKIyMjIyBFdmFsdWFjacOzbiBkZWwgUmVuZGltaWVudG8KCiMjIyMjIERhdG9zIGRlIGVudHJlbmFtaWVudG8KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiUk1TRTogIiwgcm1zZShkYXRhID0gcHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZSwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gZXhwX2ZpdHRlZCkkLmVzdGltYXRlKQpgYGAKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiTUFFOiAiLCBtYWUoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGUsIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IGV4cF9maXR0ZWQpJC5lc3RpbWF0ZSkKYGBgCgojIyMjIyBEYXRvcyBkZSBwcnVlYmEKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGUgPC0gYXVnbWVudChtb2RlbG9fY2xhc2ljb19tdWx0aXBsZSwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKcHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZSRleHBfZml0dGVkIDwtIGV4cChwcmVkX21vZGVsb19jbGFzaWNvX211bHRpcGxlJC5maXR0ZWQpCmNhdCgiUk1TRTogIiwgcm1zZShkYXRhID0gcHJlZF9tb2RlbG9fY2xhc2ljb19tdWx0aXBsZSwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gZXhwX2ZpdHRlZCkkLmVzdGltYXRlKQpgYGAKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhdCgiTUFFOiAiLCBtYWUoZGF0YSA9IHByZWRfbW9kZWxvX2NsYXNpY29fbXVsdGlwbGUsIHRydXRoID0gcHJlY2lvLCBlc3RpbWF0ZSA9IGV4cF9maXR0ZWQpJC5lc3RpbWF0ZSkKYGBgCi0gRW4gZGF0b3MgZGUgZW50cmVuYW1pZW50bzoKICAtIFJNU0U6IDI4LDUwMCw1OTgKICAtIE1BRTogOCwwNTcsNzgxCi0gRW4gZGF0b3MgZGUgcHJ1ZWJhOgogIC0gUk1TRTogMTcsNTA4LDQ1NgogIC0gTUFFOiA3LDM1Nyw2MTYKLSBMYSB0cmFuc2Zvcm1hY2nDs24gbG9nYXLDrXRtaWNhIG1lam9yYSBlbCBNQUUgZW4gbG9zIGRhdG9zIGRlIHBydWViYSwgYXVucXVlIGVsIFJNU0UgZXMgbcOhcyBhbHRvCgojIyMgTWVqb3JhcyBSZXNwZWN0byBhbCBNb2RlbG8gQW50ZXJpb3IKLSBNZWpvciBpbnRlcnByZXRhYmlsaWRhZCBkZSBsb3MgY29lZmljaWVudGVzIGVuIHTDqXJtaW5vcyBkZSBwb3JjZW50YWplcwotIE1lam9yIGN1bXBsaW1pZW50byBkZSBsb3Mgc3VwdWVzdG9zIGRlIG5vcm1hbGlkYWQgeSBob21vY2VkYXN0aWNpZGFkCi0gUmVkdWNjacOzbiBkZWwgTUFFIGVuIGRhdG9zIGRlIHBydWViYQotIE1heW9yIGVzdGFiaWxpZGFkIGVuIGxhIHByZWRpY2Npw7NuIGRlIHZhbG9yZXMgZXh0cmVtb3MKCkxhIHRyYW5zZm9ybWFjacOzbiBsb2dhcsOtdG1pY2EgZGVsIHByZWNpbyBtZWpvcmEgbGFzIHByb3BpZWRhZGVzIGVzdGFkw61zdGljYXMgZGVsIG1vZGVsbyB5IGZhY2lsaXRhIGxhIGludGVycHJldGFjacOzbiBkZSBsb3MgZWZlY3Rvcy4gRXN0ZSBtb2RlbG8gcGFyZWNlIG3DoXMgYWRlY3VhZG8gcGFyYSBwcmVkZWNpciBlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3JlcywgZXNwZWNpYWxtZW50ZSBjdWFuZG8gc2UgY29uc2lkZXJhIGxhIGludGVycHJldGFiaWxpZGFkIHkgbGEgZXN0YWJpbGlkYWQgZGUgbGFzIHByZWRpY2Npb25lcy4KCgojIFJlZ3Jlc2nDs24gTGluZWFsIE3Dumx0aXBsZSBSb2J1c3RhCgojIyBNb2RlbG8gIkdvbGVzIHNvbiBhbW9yZXMgUk9CVVNUT1MiCgpQcmVjaW8gPSBHb2xlcwoKU2UgdXRpbGl6YSBlbCBtb2RlbG8gYmFzZSBkZSBSb2J1c3RiYXNlLCBkb25kZSBsYSBmdWNpw7NuIGRlIHDDqXJkaWRhIHV0aWxpemFkYSBlcyBsYSBiaWN1YWRyYWRhIHkgdXRpbGl6YSBlbCBlc3RpbWFkb3IgTU0uCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbW9kZWxvX2xtcm9iX2dvbGVzIDwtIGxtcm9iKGZvcm11bGEgPSBwcmVjaW8gfiBHbHMsIGRhdGE9dHJhaW5fZGF0YSkKcHJlZF9tb2RlbG9fbG1yb2JfZ29sZXMgPC0gZGF0YS5mcmFtZSgKICBHbHMgPSB0cmFpbl9kYXRhJEdscywKICBwcmVjaW9fcHJlZCA9IHByZWRpY3QobW9kZWxvX2xtcm9iX2dvbGVzLCBuZXdkYXRhID0gdHJhaW5fZGF0YSkKKQpzdW1tYXJ5KG1vZGVsb19sbXJvYl9nb2xlcykKYGBgCgpFbCBzaWd1aWVudGUgZ3LDoWZpY28gbXVlc3RyYSBsYSBsaW5lYSBkZSByZWdyZXNpw7NuIGRlbCBtb2RlbG8gbGluZWFsIGNsw6FzaWNvICh2aW9sZXRhKSBmcmVudGUgYSBsYSByZWdyZXNpw7NuIGRlbCBtb2RlbG8gcm9idXN0byAoYW1hcmlsbG8pLgoKU2UgcHVlZGUgYXByZWNpYXIgbXV5IGNsYXJhbWVudGUgY29tbyBlbCBtb2RlbG8gcm9idXN0byB0aWVuZSB1bmEgcGVuZGllbnRlIG1hcyBiYWphIGFqdXN0YW5kbyBlbiBtZW5vciBtZWRpZGEgYSBsb3MganVnYWRvcmVzIGRlIG1heW9yIGNvdGl6YWNpw7NuLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmdncGxvdCh0cmFpbl9kYXRhLCBhZXMoeCA9IEdscywgeSA9IHByZWNpbykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGZvcm11bGEgPSB5IH4geCwgY29sb3I9ImRhcmt2aW9sZXQiLCBzZSA9IEZBTFNFKSArCiAgZ2VvbV9saW5lKGRhdGEgPSBwcmVkX21vZGVsb19sbXJvYl9nb2xlcywgYWVzKHggPSBHbHMsIHkgPSBwcmVjaW9fcHJlZCksIGNvbG9yID0gInllbGxvdyIpICsKICB0aGVtZV9idygpCmBgYAoKIyMjIyBJbnRlcnByZXRhY2nDs24gZGVsIE1vZGVsbwotIEVsIG1vZGVsbyBlc3RhYmxlY2UgdW5hIHJlbGFjacOzbiBsaW5lYWwgc2ltcGxlIGVudHJlIGVsIHByZWNpbyBkZWwganVnYWRvciB5IGxvcyBnb2xlcyBhbm90YWRvcwotIFBvciBjYWRhIGdvbCBhbm90YWRvLCBlbCBwcmVjaW8gZGVsIGp1Z2Fkb3IgYXVtZW50YSBlbiDigqw4OTAsNTM1OyBtdWNobyBtZW5vciBhbCBlc3RpbWFkbyBwb3IgZWwgbcOpdG9kbyBjbMOhc2ljbyBkZSDigqwzLDI2NywzNzUKLSBFbCBpbnRlcmNlcHRvIGVzIOKCrDMsMDQwLDgyNCwgcXVlIHJlcHJlc2VudGEgZWwgcHJlY2lvIGJhc2UgZXN0aW1hZG8gcGFyYSB1biBqdWdhZG9yIHNpbiBnb2xlcyB0YW1iacOpbiBwcmVzZW50ZSB1bmEgbm90YWJsZSBiYWphIGZyZW50ZSBhbCBkZWwgbW9kZWxvIGNsw6FzaWNvIGRlIOKCrDUsMjM4LDQ1MgotIEVsIFLCsiBhanVzdGFkbyBlcyAwLjIxNzcsIGxvIHF1ZSBpbmRpY2EgcXVlIGVsIG1vZGVsbyBleHBsaWNhIGFwcm94aW1hZGFtZW50ZSBlbCAyMS44JSBkZSBsYSB2YXJpYWJpbGlkYWQgZW4gbG9zIHByZWNpb3MsIG9ic2VydmFuZG8gdW5hIGRpc211bmljacOzbiBjb25zaWRlcmFibGUgZnJlbnRlIGFsIDMwJSBkZWwgbW9kZWxvIGNsw6FzaWNvLgoKIyMjIEV2YWx1YWNpw7NuIHkgY29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgZnJlbnRlIGEgZGF0b3MgZGUgcHJ1ZWJhCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbW9kZWxvc19zaW1wbGVzIDwtIGxpc3Qoc2ltcGxlXzEgPSBtb2RlbG9fY2xhc2ljb19nb2xlcywgCiAgICAgICAgICAgICAgICBzaW1wbGVfMiA9IG1vZGVsb19sbXJvYl9nb2xlcykKCmxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nID0gbWFwKC54ID0gbW9kZWxvc19zaW1wbGVzLCAuZiA9IGF1Z21lbnQsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpIAoKCmdvbGVzX2NsYXNpY29fdGVzdCA9IGxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nJHNpbXBsZV8xICU+JSAgCiAgbWV0cmljcyh0cnV0aD1wcmVjaW8sIGVzdGltYXRlPS5maXR0ZWQpICU+JQogIG11dGF0ZSguZXN0aW1hdGU9cm91bmQoLmVzdGltYXRlLCA0KSkKCmdvbGVzX3JvYnVzdG9fdGVzdCA9IGxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nJHNpbXBsZV8yICU+JSAgCiAgbWV0cmljcyh0cnV0aD1wcmVjaW8sIGVzdGltYXRlPS5maXR0ZWQpICU+JQogIG11dGF0ZSguZXN0aW1hdGU9cm91bmQoLmVzdGltYXRlLCA0KSkKCgptZXRyaWNhX2dvbGVzIDwtIHJiaW5kKGdvbGVzX2NsYXNpY29fdGVzdFtjKDEsMyksXSwgZ29sZXNfcm9idXN0b190ZXN0W2MoMSwzKSxdKQoKbW9kZWxpdG9zX3NpbXBsZXMgPC0gYyhyZXAoIkNsYXNpY28gLSBHb2xlcyIsMikscmVwKCJSb2J1c3RvIC0gR29sZXMiLDIpKQptZXRyaWNhcyA8LSBjYmluZChtb2RlbGl0b3Nfc2ltcGxlcywgbWV0cmljYV9nb2xlcykKa2FibGUobWV0cmljYXMpCmBgYAoKLSBFbCBSTVNFIGVzIG1lbm9yIHBhcmEgZWwgbW9kZWxvIGNsw6FzaWNvLCBzaW4gZW1iYXJnbywgYWwgZXN0YXIgdHJhYmFqYW5kbyBjb24gZGF0b3MgcXVlIHRpZW5lbiBvdXRsaWVycyBsbyBjb3JyZWN0byBlcyBjb21wYXJhcmxvIGNvbnRyYSBsb3MgdmFsb3JlcyBkZSBNQUUuCi0gRWwgTUFFIGVzIG1lbm9yIHBhcmEgZWwgbW9kZWxvIHJvYnVzdG8sIG1vc3RyYW5kbyB1biBtZWpvciBkZXNlbXBlw7FvIGZyZW50ZSBhbCBtb2RlbG8gY2zDoXNpY28uCgoKIyMgUHJpbWVyIE1vZGVsbyBNdWx0aXBsZSBSb2J1c3RvCgokUHJlY2lvJCA9ICRHb2xlcyQgKyAkRWRhZCQgKyAkRWRhZF4yJCArICRBc2lzdGVuY2lhcyQgKyAkQ29udGluZW50ZSQgJGRlJCAkbmFjaW1pZW50byQgJGRlbCQgJGp1Z2Fkb3IkICsgJExpZ2EkICRkb25kZSQgJGp1ZWdhJAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1vZGVsb19tdWx0aXBsZV9sbXJvYiA8LSBsbXJvYihmb3JtdWxhID0gcHJlY2lvIH4gR2xzICsgQWdlICsgSShBZ2VeMikgKyBBc3QgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250aW5lbnRlICsgY3VycmVudF9jbHViX2RvbWVzdGljX2NvbXBldGl0aW9uX2lkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPXRyYWluX2RhdGEpCnN1bW1hcnkobW9kZWxvX211bHRpcGxlX2xtcm9iKQpgYGAKCgojIyMjIEludGVycHJldGFjacOzbiBkZWwgTW9kZWxvCi0gRWwgbW9kZWxvIGluY29ycG9yYSBtw7psdGlwbGVzIHZhcmlhYmxlcyBwcmVkaWN0b3JhczogZ29sZXMsIGVkYWQgKGxpbmVhbCB5IGN1YWRyw6F0aWNhKSwgYXNpc3RlbmNpYXMsIGNvbnRpbmVudGUgZGUgb3JpZ2VuIHkgbGlnYQotIExvcyBjb2VmaWNpZW50ZXMgbcOhcyBzaWduaWZpY2F0aXZvcyBzb246CiAgLSBHb2xlczogKzU0NSw0MDkgcG9yIGdvbCAocC12YWx1ZSA9IDkuNzFlLTA5KS4gRXN0byBpbmRpY2EgcXVlIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzIHF1ZSB0aWVuZW4gbWlzbWEgY2FudGlkYWQgZGUgYXNpc3RlbmNpYXMgeSBlZGFkIGF1bWVudGEg4oKs4oKsNTQ1LDQwOSBwb3IgY2FkYSBnb2wgYW5vdGFkbwogIC0gQXNpc3RlbmNpYXM6ICs4MTQsMjgzIHBvciBhc2lzdGVuY2lhIChwLXZhbHVlID0gMi41MWUtMTApLiBFc3RvIGluZGljYSBxdWUgZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMgcXVlIHRpZW5lbiBtaXNtYSBjYW50aWRhZCBkZSBnb2xlcyB5IGVkYWQgYXVtZW50YSDigqw4MTQsMjgzIHBvciBjYWRhIGFzaXN0ZW5jaWEgYWRpY2lvbmFsLgogIC0gRWRhZDogZWZlY3RvIGN1YWRyw6F0aWNvCiAgLSBMYSB2YXJpYWJsZSBjb250aW5lbnRlIHlhIG5vIGVzIHNpZ25pZmljYXRpdmEgYSBsYSBob3JhIGRlIGV4cGxpY2FyIGVsIHByZWNpbyBkZSBsb3MganVnYWRvcmVzLCBwZXJvIHNlIG9ic2VydmEgY29tbyBzZSBpbnZpZXJ0ZSBsYSByZWxhY2nDs24gY29uIGxvcyBqdWdhZG9yZXMgZXVyb3BlYXMgZG9uZGUgbG9zIGFmcmljYW5vcyBzb24gbWFzIHZhbG9yYWRvcyBlbiBlc3RlIG1vZGVsbyBxdWUgZW4gZWwgc2ltcGxlLgogIC0gUHJlbWllciBMZWFndWU6ICszLDM1OCwxMDkgcmVzcGVjdG8gYSBsYSBsaWdhIGRlIHJlZmVyZW5jaWEsIExhIExpZ2EgKEVzcGHDsWEpIChwLXZhbHVlID0gMC4wMDAzNTcpLiBMb3MganVnYWRvcmVzIGRlIGxhIGxpZ2EgaW5nbGVzYSBzaWd1ZW4gc2llbmRvIGxvcyBtYXMgdmFsb3JhZG9zIHBlcm8gZW4gbWVub3IgbWVkaWRhIHF1ZSBlbCBtb2RlbG8gc2ltcGxlLgogIC0gRWwgcmVzdG8gZGUgbGFzIGxpZ2FzIGRlamFuIGRlIHNlciBzaWduaWZpY2F0aXZhcywgcGVybyBzZSBtYW50aWVuZSBsYSB0ZW5kZW5jaWEgcXVlIGVzdG9zIGp1Z2Fkb3JlcyBzb24gbWVub3IgdmFsb3JhZG9zIHF1ZSBsb3MgZGUgbGEgbGlnYSBlc3Bhw7FvbGEuCi0gRWwgUsKyIGFqdXN0YWRvIGVzIDAuMzY5OSwgaW5kaWNhbmRvIHF1ZSBlbCBtb2RlbG8gZXhwbGljYSBhcHJveGltYWRhbWVudGUgZWwgMzclIGRlIGxhIHZhcmlhYmlsaWRhZCwgZGlzbWludXllbmRvIGNvbiByZXNwZWN0byBhbCA0NyUgZGVsIG1vZGVsbyBzaW1wbGUKCgojIyMgRXZhbHVhY2nDs24geSBjb21wYXJhY2nDs24gZGUgbW9kZWxvcyBmcmVudGUgYSBkYXRvcyBkZSBwcnVlYmEKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptb2RlbG9zX2NvbXBhcmFjaW9uIDwtIGxpc3QobXVsdGlwbGVfMSA9IG1vZGVsb19jbGFzaWNvX211bHRpcGxlXzEsCiAgICAgICAgICAgICAgICByb2J1c3RvXzEgPSBtb2RlbG9fbXVsdGlwbGVfbG1yb2IpCgpsaXN0YV9wcmVkaWNjaW9uZXNfdGVzdGluZyA9IG1hcCgueCA9IG1vZGVsb3NfY29tcGFyYWNpb24sIC5mID0gYXVnbWVudCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkgCgoKbWV0cmljYXNfY2xhc2ljb190ZXN0ID0gbGlzdGFfcHJlZGljY2lvbmVzX3Rlc3RpbmckbXVsdGlwbGVfMSAlPiUgCiAgbWV0cmljcyh0cnV0aD1wcmVjaW8sIGVzdGltYXRlPS5maXR0ZWQpICU+JQogIG11dGF0ZSguZXN0aW1hdGU9cm91bmQoLmVzdGltYXRlLCA0KSkKCm1ldHJpY2FzX3JvYnVzdG9fdGVzdCA9IGxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nJHJvYnVzdG9fMSAlPiUKICBtZXRyaWNzKHRydXRoPXByZWNpbywgZXN0aW1hdGU9LmZpdHRlZCkgJT4lCiAgbXV0YXRlKC5lc3RpbWF0ZT1yb3VuZCguZXN0aW1hdGUsIDQpKQoKCm1ldHJpY2EgPC0gcmJpbmQobWV0cmljYXNfY2xhc2ljb190ZXN0W2MoMSwzKSxdLCBtZXRyaWNhc19yb2J1c3RvX3Rlc3RbYygxLDMpLF0pCgptb2RlbGl0b3NfY29tcGFyYWNpb24gPC0gYyhyZXAoIk11bHRpcGxlIC0gUHJlY2lvIiwyKSwKICAgICAgICAgICAgICAgcmVwKCJSb2J1c3RvIC0gUHJlY2lvIC0gcHNpID0gYmlzcXVhcmUiLDIpKQptZXRyaWNhcyA8LSBjYmluZChtb2RlbGl0b3NfY29tcGFyYWNpb24sIG1ldHJpY2EpCmthYmxlKG1ldHJpY2FzKQpgYGAKCi0gRWwgUk1TRSBlcyBtZW5vciBwYXJhIGVsIG1vZGVsbyBjbMOhc2ljbywgc2luIGVtYmFyZ28sIGFsIGVzdGFyIHRyYWJhamFuZG8gY29uIGRhdG9zIHF1ZSB0aWVuZW4gb3V0bGllcnMgbG8gY29ycmVjdG8gZXMgY29tcGFyYXJsbyBjb250cmEgbG9zIHZhbG9yZXMgZGUgTUFFLgotIEVsIE1BRSBlcyBtZW5vciBwYXJhIGVsIG1vZGVsbyByb2J1c3RvLCBtb3N0cmFuZG8gdW4gbWVqb3IgZGVzZW1wZcOxbyBmcmVudGUgYWwgbW9kZWxvIGNsw6FzaWNvLgoKCiMjIFNlZ3VuZG8gTW9kZWxvIE11bHRpcGxlIFJvYnVzdG8KCiRsb2coUHJlY2lvKSQgPSAkR29sZXMkICsgJEVkYWQkICsgJEVkYWReMiQgKyAkQXNpc3RlbmNpYXMkICsgJENvbnRpbmVudGUkICRkZSQgJG5hY2ltaWVudG8kICRkZWwkICRqdWdhZG9yJCArICRMaWdhJCAkZG9uZGUkICRqdWVnYSQKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptb2RlbG9fbXVsdGlwbGVfbG1yb2JfMiA8LSBsbXJvYihmb3JtdWxhID0gbG9nKHByZWNpbykgfiBHbHMgKyBBZ2UgKyBJKEFnZV4yKSArIEFzdCArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250aW5lbnRlICsgY3VycmVudF9jbHViX2RvbWVzdGljX2NvbXBldGl0aW9uX2lkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YSkKc3VtbWFyeShtb2RlbG9fbXVsdGlwbGVfbG1yb2JfMikKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHJlZF9tb2RlbG9fcm9idXN0b19tdWx0aXBsZSA8LSBhdWdtZW50KG1vZGVsb19tdWx0aXBsZV9sbXJvYl8yLCBuZXdkYXRhID0gdHJhaW5fZGF0YSkKcHJlZF9tb2RlbG9fcm9idXN0b19tdWx0aXBsZSRleHBfZml0dGVkIDwtIGV4cChwcmVkX21vZGVsb19yb2J1c3RvX211bHRpcGxlJC5maXR0ZWQpCm1ldHJpY2FzMiA9IG1ldHJpY3MoZGF0YSA9IHByZWRfbW9kZWxvX3JvYnVzdG9fbXVsdGlwbGUsCiAgICAgICAgICAgICAgICAgICAgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gZXhwX2ZpdHRlZCkgJT4lIAogIG11dGF0ZSguZXN0aW1hdGUgPSByb3VuZCguZXN0aW1hdGUsIDQpKQojcmN1YWRyYWRvIDwtIG1ldHJpY2FzMSQuZXN0aW1hdGVbMl0KI2NhdCgiUi1jdWFkcmFkbyA9ICIsIHJjdWFkcmFkbykKI21ldHJpY2FzMQojIENhbGN1bGFyIGVsIFLCsiBhanVzdGFkbyBtYW51YWxtZW50ZQpuX3JvYiA8LSBucm93KHByZWRfbW9kZWxvX3JvYnVzdG9fbXVsdGlwbGUpICAjIE7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcwpwX3JvYiA8LSBsZW5ndGgoY29lZihtb2RlbG9fbXVsdGlwbGVfbG1yb2JfMikpIC0gMSAgIyBOw7ptZXJvIGRlIHByZWRpY3RvcmVzIChyZXN0YW1vcyAxIHBvciBlbCBpbnRlcmNlcHRvKQoKcjJfcm9iIDwtIG1ldHJpY2FzMiAlPiUgZmlsdGVyKC5tZXRyaWMgPT0gInJzcSIpICU+JSBwdWxsKC5lc3RpbWF0ZSkKcjJfYWp1c3RhZG9fcm9iIDwtIDEgLSAoKDEgLSByMl9yb2IpICogKG5fcm9iIC0gMSkgLyAobl9yb2IgLSBwX3JvYiAtIDEpKQoKIyBNb3N0cmFyIGVsIFLCsiBhanVzdGFkbwpjYXQoIlJeMiBhanVzdGFkbyA9ICIsIHIyX2FqdXN0YWRvX3JvYikKYGBgCgojIyMjIEludGVycHJldGFjacOzbiBkZWwgTW9kZWxvCi0gRWwgbW9kZWxvIHV0aWxpemEgbGEgdHJhbnNmb3JtYWNpw7NuIGxvZ2Fyw610bWljYSBkZSBsYSB2YXJpYWJsZSBwcmVjaW8geSBtYW50aWVuZSBsYXMgbWlzbWFzIHZhcmlhYmxlcyBwcmVkaWN0b3JhcwotIExvcyBjb2VmaWNpZW50ZXMgbcOhcyBzaWduaWZpY2F0aXZvcyBzb246CiAgLSBHb2xlczogKzAuMTExIChwLXZhbHVlIDwgMmUtMTYpLiBFc3RvIGluZGljYSBxdWUgZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMgcXVlIHRpZW5lbiBtaXNtYSBjYW50aWRhZCBkZSBhc2lzdGVuY2lhcyB5IGVkYWQgYXVtZW50YSB1biAxMS43JSBwb3IgY2FkYSBnb2wgYW5vdGFkby4gCiAgLSBBc2lzdGVuY2lhczogKzAuMTcwIChwLXZhbHVlIDwgMmUtMTYpLiBFc3RvIGluZGljYSBxdWUgZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMgcXVlIHRpZW5lbiBtaXNtYSBjYW50aWRhZCBkZSBnb2xlcyB5IGVkYWQgYXVtZW50YSB1biAxOC41JSBwb3IgY2FkYSBhc2lzdGVuY2lhIGFkaWNpb25hbC4KICAtIEVkYWQ6IGVmZWN0byBjdWFkcsOhdGljbyBzaWduaWZpY2F0aXZvIChwLXZhbHVlIDwgMmUtMTYpCiAgLSBBbcOpcmljYTogKzAuNDQwIHJlc3BlY3RvIGEgw4FmcmljYSAocC12YWx1ZSA9IDAuMDAwOCksIGluZGljYSB1biBhdW1lbnRvIGVzdGFkw61zdGljYW1ldGUgc2lnbmlmaWNhdGl2byBkZSA1NS4yJSBlbiBlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3JlcyBhbWVyaWNhbm9zIGNvbiByZXNwZWN0byBhIGxvcyBhZnJpY2Fub3MuIEVzIG1heW9yIGNvbiByZXNwZWN0byBhbCBtb2RlbG8gY2zDoXNpY28sIGNvbiBsbyBjdWFsIGFsIHF1aXRhcmxlIHBlc28gYSBsb3MganVnYWRvcmVzIG1hcyBjYXJvcyBwb2RlbW9zIGRlY2lyIHF1ZSBsb3MganVnYWRvcmVzIGFtZXJpY2Fub3Mgc29uIGHDum4gbWFzIGFwcmVjaWFkb3MgcXVlIGxvcyBhZnJpY2Fub3MuCiAgLSBQcmVtaWVyIExlYWd1ZTogKzAuOTE3IHJlc3BlY3RvIGEgbGEgbGlnYSBkZSByZWZlcmVuY2lhLCAocC12YWx1ZSA8IDMuNjZlLTE2KSBpbmRpY2EgdW4gYXVtZW50byBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZvIGRlIDE1MC4yJSBlbiBlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3JlcyBkZSBsYSBQcmVtaWVyIExlYWd1ZSBjb24gcmVzcGVjdG8gYSBsb3MganVnYWRvcmVzIGRlIExhIExpZ2EgKEVzcGHDsWEpCiAgLSBMaWd1ZSAxIChGcmFuY2lhKTogLTAuMjMxIHJlc3BlY3RvIGEgbGEgbGlnYSBkZSByZWZlcmVuY2lhLCAocC12YWx1ZSA9IDAuMDMpIGluZGljYSB1bmEgZGlzbWludWNpw7NuIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmEgZGUgMjAuNiUgZW4gZWwgcHJlY2lvIGRlIGxvcyBqdWdhZG9yZXMgZGUgbGEgTGlndWUgMSBjb24gcmVzcGVjdG8gYSBsb3MganVnYWRvcmVzIGRlIExhIExpZ2EuCiAgLSBCdW5kZXNsaWdhOiAtMC4yODIgcmVzcGVjdG8gZGUgbGEgbGlnYSBkZSByZWZlcmVuY2lhIChwLXZhbHVlID0gMC4wMDQpIGluZGljYSB1bmEgZGlzbWludWNpw7NuIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmEgZGUgMjQuNiUgZW4gZWwgcHJlaWNpbyBkZSBsb3MganVnYWRvcmVzIGRlIGxhIEJ1bmRlc2xpZ2EgY29uIHJlc3BlY3RvIGEgTGEgTGlnYS4KLSBFbCBSwrIgYWp1c3RhZG8gZXMgMC4zMCwgc2ltaWxhciBhbCBtb2RlbG8gY2zDoXNpY28uCgoKIyMjIEV2YWx1YWNpw7NuIHkgY29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgZnJlbnRlIGEgZGF0b3MgZGUgcHJ1ZWJhCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbW9kZWxvc19jb21wYXJhY2lvbiA8LSBsaXN0KG11bHRpcGxlXzIgPSBtb2RlbG9fY2xhc2ljb19tdWx0aXBsZSwKICAgICAgICAgICAgICAgIHJvYnVzdG9fMiA9IG1vZGVsb19tdWx0aXBsZV9sbXJvYl8yKQoKbGlzdGFfcHJlZGljY2lvbmVzX3Rlc3RpbmcgPSBtYXAoLnggPSBtb2RlbG9zX2NvbXBhcmFjaW9uLCAuZiA9IGF1Z21lbnQsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpIAoKCm1ldHJpY2FzMl90ZXN0ID0gbGlzdGFfcHJlZGljY2lvbmVzX3Rlc3RpbmckbXVsdGlwbGVfMiAlPiUgIAogIG11dGF0ZShleHBfZml0dGVkPSBleHAoLmZpdHRlZCkpICU+JSAKICBtZXRyaWNzKHRydXRoPXByZWNpbywgZXN0aW1hdGU9ZXhwX2ZpdHRlZCkgJT4lCiAgbXV0YXRlKC5lc3RpbWF0ZT1yb3VuZCguZXN0aW1hdGUsIDQpKQoKbWV0cmljYXMzX3Rlc3QgPSBsaXN0YV9wcmVkaWNjaW9uZXNfdGVzdGluZyRyb2J1c3RvXzIgJT4lIAogIG11dGF0ZShleHBfZml0dGVkPSBleHAoLmZpdHRlZCkpICU+JQogIG1ldHJpY3ModHJ1dGg9cHJlY2lvLCBlc3RpbWF0ZT1leHBfZml0dGVkKSAlPiUKICBtdXRhdGUoLmVzdGltYXRlPXJvdW5kKC5lc3RpbWF0ZSwgNCkpCgoKbWV0cmljYSA8LSByYmluZChtZXRyaWNhczJfdGVzdFtjKDEsMyksXSwgbWV0cmljYXMzX3Rlc3RbYygxLDMpLF0pCgptb2RlbGl0b3NfY29tcGFyYWNpb24gPC0gYyhyZXAoIk11bHRpcGxlIC0gbG9nKFByZWNpbykiLDIpLAogICAgICAgICAgICAgICByZXAoIlJvYnVzdG8gLSBsb2coUHJlY2lvKSAtIHBzaSA9IGJpc3F1YXJlIiwyKSkKbWV0cmljYXMgPC0gY2JpbmQobW9kZWxpdG9zX2NvbXBhcmFjaW9uLCBtZXRyaWNhKQprYWJsZShtZXRyaWNhcykKYGBgCgotIEVsIFJNU0UgZXMgbWVub3IgcGFyYSBlbCBtb2RlbG8gY2zDoXNpY28sIHNpbiBlbWJhcmdvLCBhbCBlc3RhciB0cmFiYWphbmRvIGNvbiBkYXRvcyBxdWUgdGllbmVuIG91dGxpZXJzIGxvIGNvcnJlY3RvIGVzIGNvbXBhcmFybG8gY29udHJhIGxvcyB2YWxvcmVzIGRlIE1BRS4KLSBFbCBNQUUgZXMgbWVub3IgcGFyYSBlbCBtb2RlbG8gcm9idXN0bywgbW9zdHJhbmRvIHVuIG1lam9yIGRlc2VtcGXDsW8gZnJlbnRlIGFsIG1vZGVsbyBjbMOhc2ljby4KCiMjIE1vZGVsb3MgUm9idXN0b3MgY29uIG90cmFzIGZ1bmNpb25lcyAkXHJobyQKClNlIGFwbGljYSBsYSBtaXNtYSBmw7NybXVsYSBxdWUgZWwgbW9kZWxvIGFudGVyaW9yIHBlcm8gZW4gbHVnYXIgZGUgdXNhciBsYSBmdW5jacOzbiAkXHBzaSQgcG9yIGRlZmVjdG8gYmlzcXVlYXJlIGhhY2Vtb3MgNSBtb2RlbG9zIG51ZXZvcyB1c2FuZG8gZW4gY2FkYSB1bm8gZGlmZXJlbnRlcyAkXHJobyQ6IGxxcSwgd2Vsc2gsIG9wdGltYWwsIGhhbXBlbCB5IGdndy4KCiMjIyBFdmFsdWFjacOzbiBkZSB0b2RvcyBsb3MgbW9kZWxvcwoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04fQptb2RlbG9fbXVsdGlwbGVfbG1yb2JfbHFxIDwtIGxtcm9iKGZvcm11bGEgPSBsb2cocHJlY2lvKSB+IEdscyArIEFnZSArIEkoQWdlXjIpICsgQXN0ICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGluZW50ZSArIGN1cnJlbnRfY2x1Yl9kb21lc3RpY19jb21wZXRpdGlvbl9pZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHNpID0gImxxcSIpCgptb2RlbG9fbXVsdGlwbGVfbG1yb2JfbHFxXzIgPC0gbG1yb2IoZm9ybXVsYSA9IHByZWNpbyB+IEdscyArIEFnZSArIEkoQWdlXjIpICsgQXN0ICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGluZW50ZSArIGN1cnJlbnRfY2x1Yl9kb21lc3RpY19jb21wZXRpdGlvbl9pZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHNpID0gImxxcSIpCgptb2RlbG9fbXVsdGlwbGVfbG1yb2Jfd2Vsc2ggPC0gbG1yb2IoZm9ybXVsYSA9IGxvZyhwcmVjaW8pIH4gR2xzICsgQWdlICsgSShBZ2VeMikgKyBBc3QgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbmVudGUgKyBjdXJyZW50X2NsdWJfZG9tZXN0aWNfY29tcGV0aXRpb25faWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwc2kgPSAid2Vsc2giKQoKbW9kZWxvX211bHRpcGxlX2xtcm9iX3dlbHNoXzIgPC0gbG1yb2IoZm9ybXVsYSA9IHByZWNpbyB+IEdscyArIEFnZSArIEkoQWdlXjIpICsgQXN0ICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250aW5lbnRlICsgY3VycmVudF9jbHViX2RvbWVzdGljX2NvbXBldGl0aW9uX2lkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPXRyYWluX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHNpID0gIndlbHNoIikKCm1vZGVsb19tdWx0aXBsZV9sbXJvYl9vcHRpbWFsIDwtIGxtcm9iKGZvcm11bGEgPSBsb2cocHJlY2lvKSB+IEdscyArIEFnZSArIEkoQWdlXjIpICsgQXN0ICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbmVudGUgKyBjdXJyZW50X2NsdWJfZG9tZXN0aWNfY29tcGV0aXRpb25faWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT10cmFpbl9kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHNpID0gIm9wdGltYWwiKQoKbW9kZWxvX211bHRpcGxlX2xtcm9iX29wdGltYWxfMiA8LSBsbXJvYihmb3JtdWxhID0gcHJlY2lvIH4gR2xzICsgQWdlICsgSShBZ2VeMikgKyBBc3QgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGluZW50ZSArIGN1cnJlbnRfY2x1Yl9kb21lc3RpY19jb21wZXRpdGlvbl9pZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPXRyYWluX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwc2kgPSAib3B0aW1hbCIpCgojIEFncnVwYXIgdG9kb3MgbG9zIG1vZGVsb3MKbW9kZWxvcyA8LSBsaXN0KAogICAgbXVsdGlwbGVfMSA9IG1vZGVsb19jbGFzaWNvX211bHRpcGxlXzEsIAogICAgbXVsdGlwbGVfMiA9IG1vZGVsb19jbGFzaWNvX211bHRpcGxlLAogICAgcm9idXN0b18xID0gbW9kZWxvX211bHRpcGxlX2xtcm9iLAogICAgcm9idXN0b18yID0gbW9kZWxvX211bHRpcGxlX2xtcm9iXzIsCiAgICByb2J1c3RvXzNfMSA9IG1vZGVsb19tdWx0aXBsZV9sbXJvYl9scXFfMiwKICAgIHJvYnVzdG9fMyA9IG1vZGVsb19tdWx0aXBsZV9sbXJvYl9scXEsCiAgICByb2J1c3RvXzRfMSA9IG1vZGVsb19tdWx0aXBsZV9sbXJvYl93ZWxzaF8yLAogICAgcm9idXN0b180ID0gbW9kZWxvX211bHRpcGxlX2xtcm9iX3dlbHNoLAogICAgcm9idXN0b181XzEgPSBtb2RlbG9fbXVsdGlwbGVfbG1yb2Jfb3B0aW1hbF8yLAogICAgcm9idXN0b181ID0gbW9kZWxvX211bHRpcGxlX2xtcm9iX29wdGltYWwKKQoKIyBEZWZpbmlyIG5vbWJyZXMgZGUgbW9kZWxvcwptb2RlbGl0b3MgPC0gYyhyZXAoIkNsYXNpY28gLSBQcmVjaW8iLDMpLAogICAgICAgICAgICAgICByZXAoIkNsYXNpY28gLSBsb2coUHJlY2lvKSIsMyksCiAgICAgICAgICAgICAgIHJlcCgiUm9idXN0byAtIFByZWNpbyIsMyksCiAgICAgICAgICAgICAgIHJlcCgiUm9idXN0byAtIGxvZyhQcmVjaW8pIC0gcHNpID0gYmlzcXVhcmUiLDMpLAogICAgICAgICAgICAgICByZXAoIlJvYnVzdG8gLSBQcmVjaW8gLSBwc2kgPSBscXEiLDMpLAogICAgICAgICAgICAgICByZXAoIlJvYnVzdG8gLSBsb2coUHJlY2lvKSAtIHBzaSA9IGxxcSIsMyksCiAgICAgICAgICAgICAgIHJlcCgiUm9idXN0byAtIFByZWNpbyAtIHBzaSA9IHdlbHNoIiwzKSwgCiAgICAgICAgICAgICAgIHJlcCgiUm9idXN0byAtIGxvZyhQcmVjaW8pIC0gcHNpID0gd2Vsc2giLDMpLCAKICAgICAgICAgICAgICAgcmVwKCJSb2J1c3RvIC0gUHJlY2lvIC0gcHNpID0gb3B0aW1hbCIsMyksCiAgICAgICAgICAgICAgIHJlcCgiUm9idXN0byAtIGxvZyhQcmVjaW8pIC0gcHNpID0gb3B0aW1hbCIsMykpCgojIENhbGN1bGFyIG3DqXRyaWNhcyBwYXJhIGVudHJlbmFtaWVudG8geSBwcnVlYmEKY2FsY3VsYXJfbWV0cmljYXMgPC0gZnVuY3Rpb24obW9kZWxvLCBkYXRvcywgdGlwbykgewogIHByZWQgPC0gYXVnbWVudChtb2RlbG8sIG5ld2RhdGEgPSBkYXRvcykKICBpZigibG9nIiAlaW4lIG5hbWVzKG1vZGVsbyRjYWxsKSkgewogICAgcHJlZCRwcmVkX2ZpbmFsIDwtIGV4cChwcmVkJC5maXR0ZWQpCiAgfSBlbHNlIHsKICAgIHByZWQkcHJlZF9maW5hbCA8LSBwcmVkJC5maXR0ZWQKICB9CiAgCiAgbWV0cmljcyhkYXRhID0gcHJlZCwgdHJ1dGggPSBwcmVjaW8sIGVzdGltYXRlID0gcHJlZF9maW5hbCkgJT4lCiAgICBtdXRhdGUoLmVzdGltYXRlID0gcm91bmQoLmVzdGltYXRlLCA0KSwKICAgICAgICAgICB0aXBvX2RhdG9zID0gdGlwbykKfQoKIyBDYWxjdWxhciBtw6l0cmljYXMKbWV0cmljYXNfZW50cmVuYW1pZW50byA8LSBtYXBfZGZyKG1vZGVsb3MsIH5jYWxjdWxhcl9tZXRyaWNhcyguLCB0cmFpbl9kYXRhLCAiRW50cmVuYW1pZW50byIpLCAuaWQgPSAibW9kZWxvIikKbWV0cmljYXNfcHJ1ZWJhIDwtIG1hcF9kZnIobW9kZWxvcywgfmNhbGN1bGFyX21ldHJpY2FzKC4sIHRlc3RfZGF0YSwgIlBydWViYSIpLCAuaWQgPSAibW9kZWxvIikKCiMgQ3JlYXIgZGF0YWZyYW1lIGRlIG3DqXRyaWNhcwpkZl9tZXRyaWNhcyA8LSBkYXRhLmZyYW1lKAogIG1vZGVsbyA9IHJlcChtb2RlbGl0b3MsIDIpLAogIHRpcG9fZGF0b3MgPSBjKG1ldHJpY2FzX2VudHJlbmFtaWVudG8kdGlwb19kYXRvcywgbWV0cmljYXNfcHJ1ZWJhJHRpcG9fZGF0b3MpLAogIG1ldHJpY2EgPSBjKG1ldHJpY2FzX2VudHJlbmFtaWVudG8kLm1ldHJpYywgbWV0cmljYXNfcHJ1ZWJhJC5tZXRyaWMpLAogIHZhbG9yID0gYyhtZXRyaWNhc19lbnRyZW5hbWllbnRvJC5lc3RpbWF0ZSwgbWV0cmljYXNfcHJ1ZWJhJC5lc3RpbWF0ZSkKKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGZpbHRlcihtZXRyaWNhICVpbiUgYygicm1zZSIsICJtYWUiLCAicnNxIikpCgojIEdyw6FmaWNvIFJNU0UKZ2dwbG90KGRmX21ldHJpY2FzICU+JSBmaWx0ZXIobWV0cmljYSA9PSAicm1zZSIpLCAKICAgICAgIGFlcyh4ID0gdmFsb3IsIHkgPSBtb2RlbG8sIGZpbGwgPSB0aXBvX2RhdG9zKSkgKwogIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzY2FsZXM6OmNvbW1hKHZhbG9yKSksIAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC45KSwKICAgICAgICAgICAgaGp1c3QgPSAxLAogICAgICAgICAgICBjb2xvciA9ICJibGFjayIsCiAgICAgICAgICAgIHNpemUgPSAzKSArCiAgbGFicyh0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgUk1TRSBwb3IgTW9kZWxvIiwKICAgICAgIHggPSAiUk1TRSIsCiAgICAgICB5ID0gIk1vZGVsbyIsCiAgICAgICBmaWxsID0gIkNvbmp1bnRvIGRlIGRhdG9zIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkVudHJlbmFtaWVudG8iID0gIiM5OUNDRkYiLCAiUHJ1ZWJhIiA9ICIjMDAzMzY2IikpCgojIEdyw6FmaWNvIE1BRQpnZ3Bsb3QoZGZfbWV0cmljYXMgJT4lIGZpbHRlcihtZXRyaWNhID09ICJtYWUiKSwgCiAgICAgICBhZXMoeCA9IHZhbG9yLCB5ID0gbW9kZWxvLCBmaWxsID0gdGlwb19kYXRvcykpICsKICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc2NhbGVzOjpjb21tYSh2YWxvcikpLCAKICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksCiAgICAgICAgICAgIGhqdXN0ID0gMSwKICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgICBzaXplID0gMykgKwogIGxhYnModGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIE1BRSBwb3IgTW9kZWxvIiwKICAgICAgIHggPSAiTUFFIiwKICAgICAgIHkgPSAiTW9kZWxvIiwKICAgICAgIGZpbGwgPSAiQ29uanVudG8gZGUgZGF0b3MiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiRW50cmVuYW1pZW50byIgPSAiIzk5RkY5OSIsICJQcnVlYmEiID0gIiMwMDY2MDAiKSkKCiMgR3LDoWZpY28gUsKyCmdncGxvdChkZl9tZXRyaWNhcyAlPiUgZmlsdGVyKG1ldHJpY2EgPT0gInJzcSIpLCAKICAgICAgIGFlcyh4ID0gdmFsb3IsIHkgPSBtb2RlbG8sIGZpbGwgPSB0aXBvX2RhdG9zKSkgKwogIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzY2FsZXM6OnBlcmNlbnQodmFsb3IpKSwgCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjkpLAogICAgICAgICAgICBoanVzdCA9IDEsCiAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwKICAgICAgICAgICAgc2l6ZSA9IDMpICsKICBsYWJzKHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBSwrIgcG9yIE1vZGVsbyIsCiAgICAgICB4ID0gIlLCsiAocG9yY2VudGFqZSkiLAogICAgICAgeSA9ICJNb2RlbG8iLAogICAgICAgZmlsbCA9ICJDb25qdW50byBkZSBkYXRvcyIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJFbnRyZW5hbWllbnRvIiA9ICIjRkZCNkMxIiwgIlBydWViYSIgPSAiIzhCMDAwMCIpKQpgYGAKCi0gRWwgTUFFIGRlIHRvZG9zIGxvcyBtb2RlbG9zIHJvYnVzdG9zIGVzIG1lbm9yIHF1ZSBlbCBkZSBsb3MgbW9kZWxvcyBjbMOhc2ljb3MKLSBFbCBtb2RlbG8gcXVlIHV0aWxpemEgbGEgZnVuY2nDs24gJFxyaG8kIG9wdGltYWwgdGllbmUgbGUgbWVub3IgTUFFLCBzaWVuZG8gZWwgZGUgbWVqb3IgZGVzZW1wZcOxby4gRXN0byBlcyBwb3NpYmxlIHlhIHlhIHF1ZSBsYSBmdW5jaW9uIG9wdGltYWwgZXMgYXByb3BpYWRhIHBhcmEgZGF0b3MgZG9uZGUgbG9zIG91dGxpZXJzIG5vIHNvbiBtdXkgcHJvbnVuY2lhZG9zLCBxdWUgcGFyZWNlIHNlciBlbCBjYXNvIGRlbCBwcmVjaW8gZGUgbG9zIGp1Z2Fkb3Jlcy4KCgojIENvbmNsdXNpb25lcwoKRWwgYW7DoWxpc2lzIGVzdGFkw61zdGljbyByZXZlbGEgcGF0cm9uZXMgZnVuZGFtZW50YWxlcyBlbiBsYSB2YWxvcmFjacOzbiBkZSBqdWdhZG9yZXMgcXVlIGRlc2Fmw61hbiB2YXJpYXMgcGVyY2VwY2lvbmVzIHRyYWRpY2lvbmFsZXMgZGVsIG1lcmNhZG8uIAoKTGEgZXZpZGVuY2lhIGVtcMOtcmljYSBkZW11ZXN0cmEgdW5hIGNsYXJhIHNlZ21lbnRhY2nDs24gZGVsIG1lcmNhZG8gZXVyb3BlbywgY29uIGluZWZpY2llbmNpYXMgc2lnbmlmaWNhdGl2YXMgcXVlIGNyZWFuIG9wb3J0dW5pZGFkZXMgZXN0cmF0w6lnaWNhcy4KCkxhIGVsZWNjacOzbiBtZXRvZG9sw7NnaWNhIGRlIHV0aWxpemFyIHJlZ3Jlc2nDs24gcm9idXN0YSBjb24gdHJhbnNmb3JtYWNpw7NuIGxvZ2Fyw610bWljYSBkZW1vc3Ryw7Mgc2VyIGNydWNpYWwgcGFyYSBsYSB2YWxpZGV6IGRlbCBhbsOhbGlzaXMuIAoKRWwgbcOpdG9kbyBNTS1lc3RpbWF0aW9uIGltcGxlbWVudGFkbyBlbiBSb2J1c3RiYXNlLCBxdWUgdXRpbGl6YSBwb3IgZGVmZWN0byBsYSBmdW5jacOzbiBiaXNxdWFyZSwgcGVybWl0acOzIG1hbmVqYXIgZWZpY2F6bWVudGUgbGEgaGV0ZXJvZ2VuZWlkYWQgaW5oZXJlbnRlIGFsIG1lcmNhZG8gZGUgZmljaGFqZXMsIGRvbmRlIGxhcyB2YWxvcmFjaW9uZXMgZXh0cmVtYXMgc29uIGNvbXVuZXMgcGVybyBubyBuZWNlc2FyaWFtZW50ZSBvdXRsaWVycyBlc3RhZMOtc3RpY29zLiAKCkxhIHRyYW5zZm9ybWFjacOzbiBsb2dhcsOtdG1pY2Egbm8gc29sbyBtZWpvcsOzIGxhcyBwcm9waWVkYWRlcyBlc3RhZMOtc3RpY2FzIGRlbCBtb2RlbG8sIHNpbm8gcXVlIHRhbWJpw6luIHByb3BvcmNpb27DsyB1bmEgaW50ZXJwcmV0YWNpw7NuIG3DoXMgaW50dWl0aXZhIGVuIHTDqXJtaW5vcyBkZSB2YXJpYWNpb25lcyBwb3JjZW50dWFsZXMsIGFsaW5lw6FuZG9zZSBuYXR1cmFsbWVudGUgY29uIGxhIGZvcm1hIGVuIHF1ZSBlbCBtZXJjYWRvIGV2YWzDumEgbG9zIGNhbWJpb3MgZW4gZWwgdmFsb3IgZGUgbG9zIGp1Z2Fkb3Jlcy4gCgpFbCB1c28gZGUgZXN0aW1hZG9yZXMgcm9idXN0b3MgcmV2ZWzDsyBxdWUgZWwgbWVyY2FkbyBkZSBmaWNoYWplcywgYXVucXVlIHZvbMOhdGlsLCBtYW50aWVuZSB1bmEgZXN0cnVjdHVyYSBzdWJ5YWNlbnRlIHF1ZSBwdWVkZSBzZXIgbW9kZWxhZGEgZGUgbWFuZXJhIG3DoXMgcHJlY2lzYSBjdWFuZG8gc2UgdXRpbGl6YW4gbcOpdG9kb3MgcXVlIGVxdWlsaWJyYW4gcm9idXN0ZXogeSBlZmljaWVuY2lhIGVzdGFkw61zdGljYS4KCkVsIG1vZGVsbyByb2J1c3RvLCBjb24gdW4gUsKyIGFqdXN0YWRvIGRlIDMwJSwgY2FwdHVyYSBsYXMgZGluw6FtaWNhcyBmdW5kYW1lbnRhbGVzIGRlbCBtZXJjYWRvIG1pZW50cmFzIGZpbHRyYSBlbCAicnVpZG8iIGVzcGVjdWxhdGl2by4gCgpMYSBlZmVjdGl2aWRhZCBkZSBsYSBmdW5jacOzbiBiaXNxdWFyZSBzdWdpZXJlIHF1ZSwgY29udHJhcmlvIGEgbGEgcGVyY2VwY2nDs24gcG9wdWxhciwgbGFzIHZhbG9yYWNpb25lcyBleHRyZW1hcyBlbiBlbCBtZXJjYWRvIHNpZ3VlbiBwYXRyb25lcyBpZGVudGlmaWNhYmxlcyB5IG5vIHNvbiBwdXJhbWVudGUgZXNwZWN1bGF0aXZhcy4KCkxhIFByZW1pZXIgTGVhZ3VlIG1hbnRpZW5lIHVuYSBwcmltYSBkZSB2YWxvcmFjacOzbiBkZWwgMTUwLjIlIHNvYnJlIExhIExpZ2EsIHVuIGRpZmVyZW5jaWFsIHF1ZSBleGNlZGUgc2lnbmlmaWNhdGl2YW1lbnRlIGxhcyBkaWZlcmVuY2lhcyBlbiBpbmdyZXNvcyBvcGVyYXRpdm9zIGVudHJlIGVzdGFzIGxpZ2FzLiAKCkVzdGEgc29icmV2YWxvcmFjacOzbiBzaXN0ZW3DoXRpY2Egc3VnaWVyZSB1bmEgYnVyYnVqYSBlc3RydWN0dXJhbCBlbiBlbCBtZXJjYWRvIGluZ2zDqXMsIHBhcnRpY3VsYXJtZW50ZSBub3RhYmxlIGVuIGNvbXBhcmFjacOzbiBjb24gQnVuZGVzbGlnYSAoLTI0LjYlKSB5IExpZ3VlIDEgKC0yMC42JSksIApkb25kZSBlbCB0YWxlbnRvIHBhcmVjZSBlc3RhciBzaXN0ZW3DoXRpY2FtZW50ZSBpbmZyYXZhbG9yYWRvLgoKVW5hIG9ic2VydmFjacOzbiBwYXJ0aWN1bGFybWVudGUgcmVsZXZhbnRlIGVzIGxhIG1heW9yIHZhbG9yYWNpw7NuIGRlIGxhcyBhc2lzdGVuY2lhcyAoKzE4LjUlKSBzb2JyZSBsb3MgZ29sZXMgKCsxMS43JSkuIApFc3RhIGRpZmVyZW5jaWEgcmVmbGVqYSB1bmEgZXZvbHVjacOzbiBlbiBsYSBjb21wcmVuc2nDs24gZGVsIHZhbG9yIGNyZWF0aXZvIGVuIGVsIGbDunRib2wgbW9kZXJubywgZG9uZGUgbGEgY2FwYWNpZGFkIGRlIGdlbmVyYXIgb3BvcnR1bmlkYWRlcyBzZSBwcmVtaWEgcG9yIGVuY2ltYSBkZSBsYSBmaW5hbGl6YWNpw7NuLiBFc3RlIHBhdHLDs24gc3VnaWVyZSB1bmEgc29maXN0aWNhY2nDs24gY3JlY2llbnRlIGVuIGxhIGV2YWx1YWNpw7NuIGRlbCB0YWxlbnRvLgoKTGEgcmVsYWNpw7NuIGN1YWRyw6F0aWNhIGNvbiBsYSBlZGFkIGVtZXJnZSBjb21vIHVuIGZhY3RvciBjcsOtdGljbyBlbiBsYSB2YWxvcmFjacOzbiwgc2XDsWFsYW5kbyB1bmEgdmVudGFuYSDDs3B0aW1hIGRlIHZhbG9yYWNpw7NuIHF1ZSBlbCBtZXJjYWRvIHJlY29ub2NlIGNvbnNpc3RlbnRlbWVudGUuIApFc3RlIHBhdHLDs24gdGllbmUgaW1wbGljYWNpb25lcyBwcm9mdW5kYXMgcGFyYSBsYSBnZXN0acOzbiBkZSBhY3Rpdm9zIGRlcG9ydGl2b3MgeSBsYSBwbGFuaWZpY2FjacOzbiBkZSBwbGFudGlsbGFzIGEgbGFyZ28gcGxhem8uCgpMYSBicmVjaGEgZGUgdmFsb3JhY2nDs24gZGVsIDU1LjIlIGVudHJlIGp1Z2Fkb3JlcyBhbWVyaWNhbm9zIHkgYWZyaWNhbm9zLCBjb250cm9sYW5kbyBwb3IgcmVuZGltaWVudG8geSBsaWdhLCByZXZlbGEgdW4gc2VzZ28gZGUgbWVyY2FkbyBzaWduaWZpY2F0aXZvLiAKRXN0YSBkaXNwYXJpZGFkLCBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhIChwLXZhbHVlID0gMC4wMDA4KSwgaW5kaWNhIHVuYSBpbmVmaWNpZW5jaWEgZGUgbWVyY2FkbyBlc3RydWN0dXJhbCBxdWUgdHJhc2NpZW5kZSBlbCByZW5kaW1pZW50byBkZXBvcnRpdm8gcHVyby4KCkxhcyBpbmVmaWNpZW5jaWFzIGlkZW50aWZpY2FkYXMgZW4gZWwgbWVyY2FkbyBzdWdpZXJlbiBxdWUgZWwgdmFsb3IgcmVhbCBkZSB1biBqdWdhZG9yIHB1ZWRlIGRpZmVyaXIgc2lnbmlmaWNhdGl2YW1lbnRlIGRlIGxhcyB2YWxvcmFjaW9uZXMgZGUgbWVyY2FkbyBhY3R1YWxlcywgZXNwZWNpYWxtZW50ZSBlbiBsaWdhcyBzZWN1bmRhcmlhcyB5IG1lcmNhZG9zIGVtZXJnZW50ZXMuIApMYSB0cmFuc2Zvcm1hY2nDs24gbG9nYXLDrXRtaWNhIGRlbCBtb2RlbG8gcmV2ZWxhIHF1ZSBlc3RhcyBkaXNjcmVwYW5jaWFzIHNpZ3VlbiBwYXRyb25lcyBwcmVkZWNpYmxlcyB5IGV4cGxvdGFibGVzLgoKRWwgYW7DoWxpc2lzIHRhbWJpw6luIHNlw7FhbGEgdW5hIGV2b2x1Y2nDs24gZW4gbGEgZXN0cnVjdHVyYSBkZWwgbWVyY2FkbywgZG9uZGUgbG9zIGZhY3RvcmVzIHRyYWRpY2lvbmFsZXMgZGUgdmFsb3JhY2nDs24gZXN0w6FuIHNpZW5kbyBjb21wbGVtZW50YWRvcyBwb3IgbcOpdHJpY2FzIG3DoXMgc29maXN0aWNhZGFzLiAKTGEgc2lnbmlmaWNhdGl2YSBwcmltYSBwb3IgY3JlYXRpdmlkYWQgc3VnaWVyZSB1biBtZXJjYWRvIHF1ZSBlc3TDoSBjb21lbnphbmRvIGEgdmFsb3JhciBtw6FzIGFjZXJ0YWRhbWVudGUgbGFzIGNvbnRyaWJ1Y2lvbmVzIHTDoWN0aWNhcyBjb21wbGVqYXMuCgpFc3RvcyBoYWxsYXpnb3MgaW5kaWNhbiBxdWUgZWwgbWVyY2FkbyBkZSB0cmFuc2ZlcmVuY2lhcywgYXVucXVlIGNhZGEgdmV6IG3DoXMgc29maXN0aWNhZG8sIG1hbnRpZW5lIGluZWZpY2llbmNpYXMgZXN0cnVjdHVyYWxlcyBzaWduaWZpY2F0aXZhcy4gCkxhIGNvbWJpbmFjacOzbiBkZSBzZXNnb3MgZ2VvZ3LDoWZpY29zLCBwcmltYXMgZGUgbGlnYSB5IHZhbG9yYWNpw7NuIGRlIGhhYmlsaWRhZGVzIGVzcGVjw61maWNhcyBjcmVhIHVuIHBhbm9yYW1hIGNvbXBsZWpvIHBlcm8gYW5hbMOtdGljYW1lbnRlIG5hdmVnYWJsZSBwYXJhIGxhIGlkZW50aWZpY2FjacOzbiBkZSB2YWxvci4KCiMgQmlibGlvZ3JhZsOtYQotIERhdmlkY2FyaWJvby4gKG4uZC4pLiBGb290YmFsbCBEYXRhIGZyb20gVHJhbnNmZXJtYXJrdCBbRGF0YSBzZXRdLiBLYWdnbGUuIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvZGF2aWRjYXJpYm9vL3BsYXllci1zY29yZXMgCi0gT3JrdW5ha3Rhcy4gKG4uZC4pLiBQcmVtaWVyIExlYWd1ZSBBbGwgUGxheWVycyBTdGF0cyAyMy8yNCBbRGF0YSBzZXRdLiBLYWdnbGUuIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvb3JrdW5ha3Rhcy9wcmVtaWVyLWxlYWd1ZS1hbGwtcGxheWVycy1zdGF0cy0yMzI0IAotIEFwdW50ZSBkZSBSZWdyZXNpw7NuIExpbmVhbC4gTWFyw61hIEV1Z2VuaWEgU3pyZXR0ZXIgTm9zdGUsIEZhY3VsdGFkIGRlIENpZW5jaWFzIEV4YWN0YXMgeSBOYXR1cmFsZXMsIFVuaXZlcnNpZGFkIGRlIEJ1ZW5vcyBBaXJlcywgQWdvc3RvIC0gT2N0dWJyZSBkZSAyMDE3LiBodHRwOi8vbWF0ZS5kbS51YmEuYXIvfm1lc3pyZS9hcHVudGVfcmVncmVzaW9uX2xpbmVhbF9zenJldHRlci5wZGYKLSBDaHVuIFl1ICYgV2VpeGluIFlhbyAoMjAxNikuIFJvYnVzdCBsaW5lYXIgcmVncmVzc2lvbjogQSByZXZpZXcgYW5kIGNvbXBhcmlzb24uIENvbW11bmljYXRpb25zIGluIFN0YXRpc3RpY3MgLSBTaW11bGF0aW9uIGFuZApDb21wdXRhdGlvbiwgaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2RvaS9hYnMvMTAuMTA4MC8wMzYxMDkxOC4yMDE2LjEyMDIyNzEKLSBDbGF5IEZvcmQgKDIwMTgpLiBJbnRlcnByZXRpbmcgTG9nIFRyYW5zZm9ybWF0aW9ucyBpbiBhIExpbmVhciBNb2RlbC4gVW5pdmVyc2l0eSBvZiBWaXJnaW5pYSBMaWJyYXJ5LiBodHRwczovL2xpYnJhcnkudmlyZ2luaWEuZWR1L2RhdGEvYXJ0aWNsZXMvaW50ZXJwcmV0aW5nLWxvZy10cmFuc2Zvcm1hdGlvbnMtaW4tYS1saW5lYXItbW9kZWwKCg==